2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2015,2016,2017,2019, 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
=
66 "xml-stylesheet type=\"text/xsl\" href=\"referencedata.xsl\"";
67 //! Name of the root element in reference data XML files.
68 const char* const c_RootNodeName
= "ReferenceData";
69 //! Name of the XML attribute used to store identifying strings for reference data elements.
70 const char* const c_IdAttrName
= "Name";
74 /********************************************************************
81 //! Convenience typedef
82 typedef tinyxml2::XMLDocument
* XMLDocumentPtr
;
83 //! Convenience typedef
84 typedef tinyxml2::XMLNode
* XMLNodePtr
;
85 //! Convenience typedef
86 typedef tinyxml2::XMLElement
* XMLElementPtr
;
87 //! Convenience typedef
88 typedef tinyxml2::XMLText
* XMLTextPtr
;
90 //! \name Helper functions for XML reading
93 void readEntry(XMLNodePtr node
, ReferenceDataEntry
* entry
);
95 XMLNodePtr
getCDataChildNode(XMLNodePtr node
)
97 XMLNodePtr cdata
= node
->FirstChild();
98 while (cdata
!= nullptr && cdata
->ToText() != nullptr && !cdata
->ToText()->CData())
100 cdata
= cdata
->NextSibling();
105 bool hasCDataContent(XMLNodePtr node
)
107 return getCDataChildNode(node
) != nullptr;
110 //! Return a node convertible to text, either \c childNode or its first such sibling.
111 XMLNodePtr
getNextTextChildNode(XMLNodePtr childNode
)
113 // Note that when reading, we don't have to care if it is in a
114 // CDATA section, or not.
115 while (childNode
!= nullptr)
117 if (childNode
->ToText() != nullptr)
121 childNode
= childNode
->NextSibling();
126 //! Return the concatenation of all the text children of \c node, including multiple CDATA children.
127 std::string
getValueFromLeafElement(XMLNodePtr node
)
131 XMLNodePtr childNode
= getNextTextChildNode(node
->FirstChild());
132 while (childNode
!= nullptr)
134 value
+= std::string(childNode
->Value());
136 childNode
= getNextTextChildNode(childNode
->NextSibling());
139 if (hasCDataContent(node
))
141 // Prepare to strip the convenience newline added in
142 // createElementContents, when writing CDATA content for
144 if (value
.empty() || value
[0] != '\n')
146 GMX_THROW(TestException("Invalid string block in reference data"));
154 //! Make a new entry from \c element.
155 ReferenceDataEntry::EntryPointer
createEntry(XMLElementPtr element
)
157 const char* id
= element
->Attribute(c_IdAttrName
);
158 ReferenceDataEntry::EntryPointer
entry(new ReferenceDataEntry(element
->Value(), id
));
162 //! Read the child entries of \c parentElement and transfer the contents to \c entry
163 void readChildEntries(XMLNodePtr parentElement
, ReferenceDataEntry
* entry
)
165 XMLElementPtr childElement
= parentElement
->FirstChildElement();
166 while (childElement
!= nullptr)
168 ReferenceDataEntry::EntryPointer
child(createEntry(childElement
));
169 readEntry(childElement
, child
.get());
170 entry
->addChild(move(child
));
171 childElement
= childElement
->NextSiblingElement();
175 //! Return whether \c node has child XML elements (rather than text content).
176 bool isCompoundElement(XMLNodePtr node
)
178 return node
->FirstChildElement() != nullptr;
181 //! Read \c element and transfer the contents to \c entry
182 void readEntry(XMLNodePtr element
, ReferenceDataEntry
* entry
)
184 if (isCompoundElement(element
))
186 readChildEntries(element
, entry
);
188 else if (hasCDataContent(element
))
190 entry
->setTextBlockValue(getValueFromLeafElement(element
));
194 entry
->setValue(getValueFromLeafElement(element
));
203 ReferenceDataEntry::EntryPointer
readReferenceDataFile(const std::string
& path
)
205 tinyxml2::XMLDocument document
;
206 document
.LoadFile(path
.c_str());
207 if (document
.Error())
209 const char* errorStr1
= document
.GetErrorStr1();
210 const char* errorStr2
= document
.GetErrorStr2();
211 std::string
errorString("Error was ");
214 errorString
+= errorStr1
;
218 errorString
+= errorStr2
;
220 if (!errorStr1
&& !errorStr2
)
222 errorString
+= "not specified.";
224 GMX_THROW(TestException("Reference data not parsed successfully: " + path
+ "\n."
225 + errorString
+ "\n"));
227 XMLElementPtr rootNode
= document
.RootElement();
228 if (rootNode
== nullptr)
230 GMX_THROW(TestException("Reference data is empty: " + path
));
232 if (std::strcmp(rootNode
->Value(), c_RootNodeName
) != 0)
234 GMX_THROW(TestException("Invalid root node type in " + path
));
237 ReferenceDataEntry::EntryPointer
rootEntry(ReferenceDataEntry::createRoot());
238 readEntry(rootNode
, rootEntry
.get());
243 /********************************************************************
250 //! \name Helper functions for XML writing
253 void createElementAndContents(XMLElementPtr parentElement
, const ReferenceDataEntry
& entry
);
255 void setIdAttribute(XMLElementPtr element
, const std::string
& id
)
259 element
->SetAttribute(c_IdAttrName
, id
.c_str()); // If this fails, it throws std::bad_alloc
263 XMLElementPtr
createElement(XMLElementPtr parentElement
, const ReferenceDataEntry
& entry
)
265 XMLElementPtr element
= parentElement
->GetDocument()->NewElement(entry
.type().c_str());
266 parentElement
->InsertEndChild(element
);
267 setIdAttribute(element
, entry
.id()); // If this fails, it throws std::bad_alloc
271 void createChildElements(XMLElementPtr parentElement
, const ReferenceDataEntry
& entry
)
273 const ReferenceDataEntry::ChildList
& children(entry
.children());
274 ReferenceDataEntry::ChildIterator child
;
275 for (child
= children
.begin(); child
!= children
.end(); ++child
)
277 createElementAndContents(parentElement
, **child
);
281 /*! \brief Handle \c input intended to be written as CDATA
283 * This method searches for any ']]>' sequences embedded in \c input,
284 * because this must always end a CDATA field. If any are found, it
285 * breaks the string so that instead multiple CDATA fields will be
286 * written with that token sequence split across the fields. Note that
287 * tinyxml2 does not handle such things itself.
289 * This is an edge case that is unimportant for GROMACS refdata, but
290 * it is preferable to know that the infrastructure won't break.
292 std::vector
<std::string
> breakUpAnyCdataEndTags(const std::string
& input
)
294 std::vector
<std::string
> strings
;
295 std::size_t startPos
= 0;
300 endPos
= input
.find("]]>", startPos
);
301 if (endPos
!= std::string::npos
)
303 // We found an embedded CDATA end tag, so arrange to split it into multiple CDATA blocks
306 strings
.push_back(input
.substr(startPos
, endPos
));
308 } while (endPos
!= std::string::npos
);
313 void createElementContents(XMLElementPtr element
, const ReferenceDataEntry
& entry
)
315 // TODO: Figure out if \r and \r\n can be handled without them
316 // changing to \n in the roundtrip.
317 if (entry
.isCompound())
319 createChildElements(element
, entry
);
321 else if (entry
.isTextBlock())
323 // An extra newline is written in the beginning to make lines align
324 // in the output xml (otherwise, the first line would be off by the length
325 // of the starting CDATA tag).
326 const std::string adjustedValue
= "\n" + entry
.value();
327 std::vector
<std::string
> cdataStrings
= breakUpAnyCdataEndTags(adjustedValue
);
328 for (auto const& s
: cdataStrings
)
330 XMLTextPtr textNode
= element
->GetDocument()->NewText(s
.c_str());
331 textNode
->SetCData(true);
332 element
->InsertEndChild(textNode
);
337 XMLTextPtr textNode
= element
->GetDocument()->NewText(entry
.value().c_str());
338 element
->InsertEndChild(textNode
);
342 void createElementAndContents(XMLElementPtr parentElement
, const ReferenceDataEntry
& entry
)
344 XMLElementPtr element
= createElement(parentElement
, entry
);
345 createElementContents(element
, entry
);
348 XMLElementPtr
createRootElement(XMLDocumentPtr document
)
350 XMLElementPtr rootElement
= document
->NewElement(c_RootNodeName
);
351 document
->InsertEndChild(rootElement
);
360 void writeReferenceDataFile(const std::string
& path
, const ReferenceDataEntry
& rootEntry
)
362 // TODO: Error checking
363 tinyxml2::XMLDocument document
;
365 tinyxml2::XMLDeclaration
* declaration
= document
.NewDeclaration(c_VersionDeclarationString
);
366 document
.InsertEndChild(declaration
);
368 declaration
= document
.NewDeclaration(c_StyleSheetDeclarationString
);
369 document
.InsertEndChild(declaration
);
371 XMLElementPtr rootElement
= createRootElement(&document
);
372 createChildElements(rootElement
, rootEntry
);
374 if (document
.SaveFile(path
.c_str()) != tinyxml2::XML_NO_ERROR
)
376 GMX_THROW(TestException("Reference data saving failed in " + path
));