Move ns.h and nsgrid.h to mdlib/
[gromacs.git] / src / testutils / refdata-xml.cpp
blob5ca7e658d109f99f0cc2cad75d28b118773a5534
1 /*
2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2015, 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.
35 /*! \internal \file
36 * \brief
37 * Implements reference data XML persistence.
39 * \author Teemu Murtola <teemu.murtola@gmail.com>
40 * \ingroup module_testutils
42 #include "gmxpre.h"
44 #include "refdata-xml.h"
46 #include <libxml/parser.h>
47 #include <libxml/xmlmemory.h>
49 #include "gromacs/utility/exceptions.h"
50 #include "gromacs/utility/scoped_cptr.h"
52 #include "testutils/refdata-impl.h"
53 #include "testutils/testexceptions.h"
55 namespace gmx
57 namespace test
60 namespace
63 //! XML version used for writing the reference data.
64 const xmlChar *const cXmlVersion =
65 reinterpret_cast<const xmlChar *>("1.0");
66 //! Name of the XML processing instruction used for XSLT reference.
67 const xmlChar *const cXmlStyleSheetNodeName =
68 reinterpret_cast<const xmlChar *>("xml-stylesheet");
69 //! XSLT reference written to the reference data XML files.
70 const xmlChar *const cXmlStyleSheetContent =
71 reinterpret_cast<const xmlChar *>("type=\"text/xsl\" href=\"referencedata.xsl\"");
72 //! Name of the root element in reference data XML files.
73 const xmlChar *const cRootNodeName =
74 reinterpret_cast<const xmlChar *>("ReferenceData");
75 //! Name of the XML attribute used to store identifying strings for reference data elements.
76 const xmlChar *const cIdAttrName =
77 reinterpret_cast<const xmlChar *>("Name");
79 /********************************************************************
80 * Generic helper functions and classes
83 //! Helper function to convert strings to xmlChars.
84 const xmlChar *toXmlString(const std::string &str)
86 // TODO: Consider asserting that str is ASCII.
87 return reinterpret_cast<const xmlChar *>(str.c_str());
90 //! Helper function to convert strings from xmlChars.
91 const char *fromXmlString(const xmlChar *str)
93 // TODO: Consider asserting that str is ASCII.
94 return reinterpret_cast<const char *>(str);
97 class XmlString
99 public:
100 explicit XmlString(xmlChar *str) : str_(str) {}
101 ~XmlString()
103 if (str_ != NULL)
105 xmlFree(str_);
109 std::string toString() const
111 if (str_ == NULL)
113 return std::string();
115 return std::string(fromXmlString(str_));
118 private:
119 xmlChar *str_;
122 //! C++ wrapper for xmlDocPtr for exception safety.
123 typedef scoped_cptr<xmlDoc, xmlFreeDoc> XmlDocumentPointer;
125 } // namespace
127 /********************************************************************
128 * XML reading
131 namespace
134 //! \name Helper functions for XML reading
135 //! \{
137 void readEntry(xmlNodePtr node, ReferenceDataEntry *entry);
139 xmlNodePtr getCDataChildNode(xmlNodePtr node)
141 xmlNodePtr cdata = node->children;
142 while (cdata != NULL && cdata->type != XML_CDATA_SECTION_NODE)
144 cdata = cdata->next;
146 // TODO: Consider checking that there is no more than one CDATA section.
147 return cdata;
150 bool hasCDataContent(xmlNodePtr node)
152 return getCDataChildNode(node) != NULL;
155 xmlNodePtr findContentNode(xmlNodePtr node)
157 xmlNodePtr cdata = getCDataChildNode(node);
158 return cdata != NULL ? cdata : node;
161 std::string getValueFromLeafElement(xmlNodePtr node)
163 xmlNodePtr contentNode = findContentNode(node);
164 XmlString content(xmlNodeGetContent(contentNode));
165 std::string value(content.toString());
166 if (hasCDataContent(node))
168 if (value.empty() || value[0] != '\n')
170 GMX_THROW(TestException("Invalid CDATA string block in reference data"));
172 value.erase(0, 1);
174 return value;
177 ReferenceDataEntry::EntryPointer createEntry(xmlNodePtr element)
179 XmlString id(xmlGetProp(element, cIdAttrName));
180 ReferenceDataEntry::EntryPointer entry(
181 new ReferenceDataEntry(fromXmlString(element->name),
182 id.toString().c_str()));
183 return entry;
186 void readChildEntries(xmlNodePtr parentElement, ReferenceDataEntry *entry)
188 xmlNodePtr childElement = xmlFirstElementChild(parentElement);
189 while (childElement != NULL)
191 ReferenceDataEntry::EntryPointer child(createEntry(childElement));
192 readEntry(childElement, child.get());
193 entry->addChild(move(child));
194 childElement = xmlNextElementSibling(childElement);
198 bool isCompoundElement(xmlNodePtr node)
200 return xmlFirstElementChild(node) != NULL;
203 void readEntry(xmlNodePtr element, ReferenceDataEntry *entry)
205 if (isCompoundElement(element))
207 readChildEntries(element, entry);
209 else if (hasCDataContent(element))
211 entry->setTextBlockValue(getValueFromLeafElement(element));
213 else
215 entry->setValue(getValueFromLeafElement(element));
219 //! \}
221 } // namespace
223 //! \cond internal
224 ReferenceDataEntry::EntryPointer
225 readReferenceDataFile(const std::string &path)
227 XmlDocumentPointer document(xmlParseFile(path.c_str()));
228 if (!document)
230 GMX_THROW(TestException("Reference data not parsed successfully: " + path));
232 xmlNodePtr rootNode = xmlDocGetRootElement(document.get());
233 if (rootNode == NULL)
235 GMX_THROW(TestException("Reference data is empty: " + path));
237 if (xmlStrcmp(rootNode->name, cRootNodeName) != 0)
239 GMX_THROW(TestException("Invalid root node type in " + path));
242 ReferenceDataEntry::EntryPointer rootEntry(ReferenceDataEntry::createRoot());
243 readEntry(rootNode, rootEntry.get());
244 return rootEntry;
246 //! \endcond
248 /********************************************************************
249 * XML writing
252 namespace
255 //! \name Helper functions for XML writing
256 //! \{
258 void createElementAndContents(xmlNodePtr parentNode,
259 const ReferenceDataEntry &entry);
261 void setIdAttribute(xmlNodePtr node, const std::string &id)
263 if (!id.empty())
265 const xmlChar *xmlId = toXmlString(id);
266 xmlAttrPtr prop = xmlNewProp(node, cIdAttrName, xmlId);
267 if (prop == NULL)
269 GMX_THROW(TestException("XML attribute creation failed"));
274 xmlNodePtr createElement(xmlNodePtr parentNode, const ReferenceDataEntry &entry)
276 xmlNodePtr node = xmlNewTextChild(parentNode, NULL, toXmlString(entry.type()), NULL);
277 if (node == NULL)
279 GMX_THROW(TestException("XML element creation failed"));
281 setIdAttribute(node, entry.id());
282 return node;
285 void createChildElements(xmlNodePtr parentNode, const ReferenceDataEntry &entry)
287 const ReferenceDataEntry::ChildList &children(entry.children());
288 ReferenceDataEntry::ChildIterator child;
289 for (child = children.begin(); child != children.end(); ++child)
291 createElementAndContents(parentNode, **child);
295 void createElementContents(xmlNodePtr node, const ReferenceDataEntry &entry)
297 if (entry.isCompound())
299 createChildElements(node, entry);
301 else if (entry.isTextBlock())
303 // An extra newline is written in the beginning to make lines align
304 // in the output xml (otherwise, the first line would be off by the length
305 // of the starting CDATA tag).
306 const std::string adjustedValue = "\n" + entry.value();
307 // TODO: Figure out if \r and \r\n can be handled without them changing
308 // to \n in the roundtrip
309 xmlNodePtr cdata
310 = xmlNewCDataBlock(node->doc, toXmlString(adjustedValue),
311 static_cast<int>(adjustedValue.length()));
312 xmlAddChild(node, cdata);
314 else
316 xmlNodeAddContent(node, toXmlString(entry.value()));
320 void createElementAndContents(xmlNodePtr parentNode, const ReferenceDataEntry &entry)
322 xmlNodePtr node = createElement(parentNode, entry);
323 createElementContents(node, entry);
326 xmlNodePtr createRootElement(xmlDocPtr document)
328 xmlNodePtr rootElement = xmlNewDocNode(document, NULL, cRootNodeName, NULL);
329 xmlDocSetRootElement(document, rootElement);
330 return rootElement;
333 void createXsltReference(xmlDocPtr document, xmlNodePtr rootElement)
335 xmlNodePtr xslNode = xmlNewDocPI(document, cXmlStyleSheetNodeName,
336 cXmlStyleSheetContent);
337 xmlAddPrevSibling(rootElement, xslNode);
340 //! \}
342 } // namespace
344 //! \cond internal
345 void writeReferenceDataFile(const std::string &path,
346 const ReferenceDataEntry &rootEntry)
348 // TODO: Error checking
349 XmlDocumentPointer document(xmlNewDoc(cXmlVersion));
350 xmlNodePtr rootElement = createRootElement(document.get());
351 createXsltReference(document.get(), rootElement);
352 createChildElements(rootElement, rootEntry);
354 if (xmlSaveFormatFile(path.c_str(), document.get(), 1) == -1)
356 GMX_THROW(TestException("Reference data saving failed in " + path));
359 //! \endcond
361 /********************************************************************
362 * Cleanup
365 //! \cond internal
366 void cleanupReferenceData()
368 xmlCleanupParser();
370 //! \endcond
372 } // namespace test
373 } // namespace gmx