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.
37 * Implements reference data XML persistence.
39 * \author Teemu Murtola <teemu.murtola@gmail.com>
40 * \ingroup module_testutils
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"
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
);
100 explicit XmlString(xmlChar
*str
) : str_(str
) {}
109 std::string
toString() const
113 return std::string();
115 return std::string(fromXmlString(str_
));
122 //! C++ wrapper for xmlDocPtr for exception safety.
123 typedef scoped_cptr
<xmlDoc
, xmlFreeDoc
> XmlDocumentPointer
;
127 /********************************************************************
134 //! \name Helper functions for XML reading
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
)
146 // TODO: Consider checking that there is no more than one CDATA section.
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"));
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()));
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
));
215 entry
->setValue(getValueFromLeafElement(element
));
224 ReferenceDataEntry::EntryPointer
225 readReferenceDataFile(const std::string
&path
)
227 XmlDocumentPointer
document(xmlParseFile(path
.c_str()));
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());
248 /********************************************************************
255 //! \name Helper functions for XML writing
258 void createElementAndContents(xmlNodePtr parentNode
,
259 const ReferenceDataEntry
&entry
);
261 void setIdAttribute(xmlNodePtr node
, const std::string
&id
)
265 const xmlChar
*xmlId
= toXmlString(id
);
266 xmlAttrPtr prop
= xmlNewProp(node
, cIdAttrName
, xmlId
);
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
);
279 GMX_THROW(TestException("XML element creation failed"));
281 setIdAttribute(node
, entry
.id());
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
310 = xmlNewCDataBlock(node
->doc
, toXmlString(adjustedValue
),
311 static_cast<int>(adjustedValue
.length()));
312 xmlAddChild(node
, cdata
);
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
);
333 void createXsltReference(xmlDocPtr document
, xmlNodePtr rootElement
)
335 xmlNodePtr xslNode
= xmlNewDocPI(document
, cXmlStyleSheetNodeName
,
336 cXmlStyleSheetContent
);
337 xmlAddPrevSibling(rootElement
, xslNode
);
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
));
361 /********************************************************************
366 void cleanupReferenceData()