changeset 306:4fac25f2b251

Xml: * Rename XmlParser.(h|cpp) to Xml.$1 * Class XmlParser renamed to XmlReader * Use callbacks instead of virtual functions Task: #311
author David Demelier <markand@malikania.fr>
date Mon, 17 Nov 2014 20:29:09 +0100
parents ddd704ac6e21
children e2a8cbf2dd79
files C++/Tests/Xml/CMakeLists.txt C++/Tests/Xml/data/simple.xml C++/Tests/Xml/main.cpp C++/Tests/Zip/CMakeLists.txt C++/Xml.cpp C++/Xml.h C++/XmlParser.cpp C++/XmlParser.h CMakeLists.txt
diffstat 9 files changed, 427 insertions(+), 170 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/C++/Tests/Xml/CMakeLists.txt	Mon Nov 17 20:29:09 2014 +0100
@@ -0,0 +1,35 @@
+#
+# CMakeLists.txt -- tests for Xml
+#
+# Copyright (c) 2013, 2014 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+project(xml)
+
+find_package(EXPAT REQUIRED)
+
+set(
+	SOURCES
+	${code_SOURCE_DIR}/C++/Xml.cpp
+	${code_SOURCE_DIR}/C++/Xml.h
+	${xml_SOURCE_DIR}/data/simple.xml
+	main.cpp
+)
+
+define_test(xml "${SOURCES}")
+
+target_include_directories(xml PRIVATE ${EXPAT_INCLUDE_DIRS})
+target_link_libraries(xml ${EXPAT_LIBRARIES})
+target_compile_definitions(xml PRIVATE "SOURCE=\"${xml_SOURCE_DIR}\"")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/C++/Tests/Xml/data/simple.xml	Mon Nov 17 20:29:09 2014 +0100
@@ -0,0 +1,4 @@
+<xml>
+  <firstName>David</firstName>
+  <lastName>Demelier</lastName>
+</xml>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/C++/Tests/Xml/main.cpp	Mon Nov 17 20:29:09 2014 +0100
@@ -0,0 +1,99 @@
+/*
+ * main.cpp -- main test file for Xml
+ *
+ * Copyright (c) 2013, 2014 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <gtest/gtest.h>
+
+#include <Xml.h>
+
+TEST(File, simple)
+{
+	XmlReader reader;
+	std::string firstName;
+	std::string lastName;
+	std::string element;
+
+	reader.setOnElement([&] (const auto &name, const auto &) {
+		element = name;
+	});
+	reader.setOnEndElement([&] (const auto &) {
+		element = "";
+	});
+	reader.setOnContent([&] (const auto &data) {
+		if (element == "firstName")
+			firstName = data;
+		else if (element == "lastName")
+			lastName = data;
+	});
+
+	reader.open(SOURCE "/data/simple.xml");
+
+	ASSERT_EQ("David", firstName);
+	ASSERT_EQ("Demelier", lastName);
+}
+
+TEST(String, simple)
+{
+	XmlReader reader;
+	std::string element;
+	std::string name;
+
+	reader.setOnElement([&] (const auto &name, const auto &) {
+		element = name;
+	});
+	reader.setOnEndElement([&] (const auto &) {
+		element = "";
+	});
+	reader.setOnContent([&] (const auto &data) {
+		if (element == "name")
+			name = data;
+	});
+
+	reader.parse("<root><name>David</name></root>", true);
+
+	ASSERT_EQ("David", name);
+}
+
+TEST(String, split)
+{
+	XmlReader reader;
+	std::string element;
+	std::string name;
+
+	reader.setOnElement([&] (const auto &name, const auto &) {
+		element = name;
+	});
+	reader.setOnEndElement([&] (const auto &) {
+		element = "";
+	});
+	reader.setOnContent([&] (const auto &data) {
+		if (element == "name")
+			name = data;
+	});
+
+	reader.parse("<root><name>David", false);
+	reader.parse("</name></root>", true);
+
+	ASSERT_EQ("David", name);
+}
+
+int main(int argc, char **argv)
+{
+	testing::InitGoogleTest(&argc, argv);
+
+	return RUN_ALL_TESTS();
+}
--- a/C++/Tests/Zip/CMakeLists.txt	Sat Nov 15 13:29:30 2014 +0100
+++ b/C++/Tests/Zip/CMakeLists.txt	Mon Nov 17 20:29:09 2014 +0100
@@ -24,6 +24,7 @@
 	SOURCES
 	${code_SOURCE_DIR}/C++/ZipArchive.cpp
 	${code_SOURCE_DIR}/C++/ZipArchive.h
+	${zip_SOURCE_DIR}/data/data.txt
 	main.cpp
 )
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/C++/Xml.cpp	Mon Nov 17 20:29:09 2014 +0100
@@ -0,0 +1,91 @@
+/*
+ * Xml.h -- C++ wrappers around libexpat
+ *
+ * Copyright (c) 2013, 2014 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <fstream>
+
+#include "Xml.h"
+
+namespace {
+
+void xmlStartElementHandler(XmlReader *p, const XML_Char *name, const XML_Char **attrs)
+{
+	XmlReader::Attrs attributes;
+
+	for (const XML_Char **p = attrs; *p != nullptr; p += 2)
+		attributes[p[0]] = p[1];
+
+	p->onElement()(name, attributes);
+}
+
+void xmlEndElementHandler(XmlReader *p, const XML_Char *name)
+{
+	p->onEndElement()(name);
+}
+
+void xmlCharacterDataHandler(XmlReader *p, const XML_Char *data, int length)
+{
+	p->onContent()({data, static_cast<size_t>(length)});
+}
+
+void xmlStartCdataSectionHandler(XmlReader *p)
+{
+	p->onCData()();
+}
+
+void xmlEndCdataSectionHandler(XmlReader *p)
+{
+	p->onEndCData()();
+}
+
+} // !namespace
+
+XmlReader::XmlReader()
+	: m_handle{nullptr, nullptr}
+{
+	auto p = XML_ParserCreate(nullptr);
+
+	XML_SetUserData(p, this);
+	XML_SetElementHandler(p,
+	    reinterpret_cast<XML_StartElementHandler>(xmlStartElementHandler),
+	    reinterpret_cast<XML_EndElementHandler>(xmlEndElementHandler));
+	XML_SetCharacterDataHandler(p,
+	    reinterpret_cast<XML_CharacterDataHandler>(xmlCharacterDataHandler));
+	XML_SetCdataSectionHandler(p,
+	    reinterpret_cast<XML_StartCdataSectionHandler>(xmlStartCdataSectionHandler),
+	    reinterpret_cast<XML_EndCdataSectionHandler>(xmlEndCdataSectionHandler));
+
+	m_handle = Handle(p, XML_ParserFree);
+}
+
+void XmlReader::open(const std::string &path)
+{
+	std::string line;
+	std::ifstream ifile{path};
+
+	if (ifile.is_open()) {
+		while (std::getline(ifile, line))
+			XML_Parse(m_handle.get(), line.c_str(), line.length(), false);
+
+		XML_Parse(m_handle.get(), "", 0, true);
+	}
+}
+
+void XmlReader::parse(const std::string &data, bool isfinal)
+{
+	XML_Parse(m_handle.get(), data.c_str(), data.length(), isfinal);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/C++/Xml.h	Mon Nov 17 20:29:09 2014 +0100
@@ -0,0 +1,192 @@
+/*
+ * Xml.h -- C++ wrappers around libexpat
+ *
+ * Copyright (c) 2013, 2014 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _XML_WRAPPERS_H_
+#define _XML_WRAPPERS_H_
+
+#include <functional>
+#include <memory>
+#include <string>
+#include <type_traits>
+#include <unordered_map>
+
+#include <expat.h>
+
+/**
+ * @class XmlReader
+ * @brief Wrapper around libexpat for reading
+ *
+ * As a wrapper on libexpat, this class works exclusively with callbacks, which
+ * makes it performant on large files.
+ */
+class XmlReader {
+public:
+	using Attrs		= std::unordered_map<std::string, std::string>;
+	using OnElement		= std::function<void (const std::string &, const Attrs &)>;
+	using OnEndElement	= std::function<void (const std::string &)>;
+	using OnContent		= std::function<void (const std::string &)>;
+	using OnCData		= std::function<void ()>;
+	using OnEndCData	= std::function<void ()>;
+	using ParserType	= std::remove_pointer<XML_Parser>::type;
+
+	static inline void defOnElement(const std::string &, const Attrs &) {}
+	static inline void defOnEndElement(const std::string &) {}
+	static inline void defOnContent(const std::string &) {}
+	static inline void defOnCData() {}
+	static inline void defOnEndCData() {}
+
+private:
+	using Handle	= std::unique_ptr<ParserType, void (*)(XML_Parser)>;
+
+	Handle		m_handle;
+	OnElement	m_onElement{defOnElement};
+	OnEndElement	m_onEndElement{defOnEndElement};
+	OnContent	m_onContent{defOnContent};
+	OnCData		m_onCData{defOnCData};
+	OnEndCData	m_onEndCData{defOnEndCData};
+
+public:
+	/**
+	 * Default constructor.
+	 */
+	XmlReader();
+
+	/**
+	 * Read the file. The file will be parsed line per line so passing
+	 * a large file is efficient.
+	 *
+	 * @param path the data
+	 */
+	void open(const std::string &path);
+
+	/**
+	 * Parse XML data from a buffer.
+	 *
+	 * @param data the data
+	 * @param isfinal tells if the buffer is finished (no more data is available)
+	 */
+	void parse(const std::string &data, bool isfinal = false);
+
+	/**
+	 * Get the element handler.
+	 *
+	 * @return element handler
+	 */
+	inline OnElement &onElement()
+	{
+		return m_onElement;
+	}
+
+	/**
+	 * Set the element handler.
+	 *
+	 * @param func the handler
+	 */
+	template <typename Func>
+	inline void setOnElement(Func &&func)
+	{
+		m_onElement = std::forward<Func>(func);
+	}
+
+	/**
+	 * Get the end element handler.
+	 *
+	 * @return element handler
+	 */
+	inline OnEndElement &onEndElement()
+	{
+		return m_onEndElement;
+	}
+
+	/**
+	 * Set the end element handler.
+	 *
+	 * @param func the handler
+	 */
+	template <typename Func>
+	inline void setOnEndElement(Func &&func)
+	{
+		m_onEndElement = std::forward<Func>(func);
+	}
+
+	/**
+	 * Get the content element handler.
+	 *
+	 * @return element handler
+	 */
+	inline OnContent &onContent()
+	{
+		return m_onContent;
+	}
+
+	/**
+	 * Set the content element handler.
+	 *
+	 * @param func the handler
+	 */
+	template <typename Func>
+	inline void setOnContent(Func &&func)
+	{
+		m_onContent = std::forward<Func>(func);
+	}
+
+	/**
+	 * Get the begin cdata element handler.
+	 *
+	 * @return element handler
+	 */
+	inline OnCData &onCData()
+	{
+		return m_onCData;
+	}
+
+	/**
+	 * Set the begin cdata element handler.
+	 *
+	 * @param func the handler
+	 */
+	template <typename Func>
+	inline void setOnCData(Func &&func)
+	{
+		m_onCData = std::forward<Func>(func);
+	}
+
+	/**
+	 * Get the end cdata element handler.
+	 *
+	 * @return element handler
+	 */
+	inline OnEndCData &onEndCData()
+	{
+		return m_onEndCData;
+	}
+
+	/**
+	 * Set the end cdata element handler.
+	 *
+	 * @param func the handler
+	 */
+	template <typename Func>
+	inline void setOnEndCData(Func &&func)
+	{
+		m_onEndCData = std::forward<Func>(func);
+	}
+};
+
+
+#endif // !_XML_WRAPPERS_H_
--- a/C++/XmlParser.cpp	Sat Nov 15 13:29:30 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,79 +0,0 @@
-/*
- * XmlParser.h -- C++ wrapper around libexpat
- *
- * Copyright (c) 2013 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <fstream>
-
-#include "XmlParser.h"
-
-namespace {
-
-void xmlStartElementHandler(XmlParser *p, const XML_Char *name, const XML_Char **attrs)
-{
-	XmlParser::Attrs attributes;	
-
-	for (const XML_Char **p = attrs; *p != NULL; p += 2)
-		attributes[p[0]] = p[1];
-
-	p->startElementHandler(name, attributes);
-}
-
-void xmlEndElementHandler(XmlParser *p, const XML_Char *name)
-{
-	p->endElementHandler(name);
-}
-
-void xmlCharacterDataHandler(XmlParser *p, const XML_Char *data, int length)
-{
-	std::string str;
-
-	str.reserve(length);
-	str.insert(0, data, length);
-
-	p->characterDataHandler(str);
-}
-
-}
-
-XmlParser::XmlParser(const std::string &path)
-	: m_path(path)
-{
-	auto p = XML_ParserCreate(nullptr);
-
-	XML_SetUserData(p, this);
-	XML_SetElementHandler(p,
-	    reinterpret_cast<XML_StartElementHandler>(xmlStartElementHandler),
-	    reinterpret_cast<XML_EndElementHandler>(xmlEndElementHandler));
-	XML_SetCharacterDataHandler(p,
-	    reinterpret_cast<XML_CharacterDataHandler>(xmlCharacterDataHandler));
-
-	m_handle = Ptr(new XML_Parser(p));
-}
-
-void XmlParser::open()
-{
-	std::string line;
-	std::ifstream ifile(m_path);
-	int done = 0;
-
-	if (ifile.is_open() && !done) {
-		while (std::getline(ifile, line)) {
-			done = ifile.eof();
-			XML_Parse(*m_handle.get(), line.c_str(), line.length(), done);
-		}
-	}
-}
--- a/C++/XmlParser.h	Sat Nov 15 13:29:30 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,90 +0,0 @@
-/*
- * XmlParser.h -- C++ wrapper around libexpat
- *
- * Copyright (c) 2013 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef _XML_PARSER_H_
-#define _XML_PARSER_H_
-
-#include <memory>
-#include <string>
-#include <unordered_map>
-
-#include <expat.h>
-
-/**
- * @class XmlParser
- * @brief Wrapper around libexpat
- */
-class XmlParser {
-public:
-	using Attrs	= std::unordered_map<std::string, std::string>;
-
-private:
-	struct Deleter {
-		void operator()(XML_Parser *p)
-		{
-			XML_ParserFree(*p);
-
-			delete p;
-		}
-	};
-
-	using Ptr	= std::unique_ptr<XML_Parser, Deleter>;
-
-	Ptr		m_handle;
-	std::string	m_path;
-
-public:
-	/**
-	 * Constructor and open a file.
-	 *
-	 * @param path the path
-	 */
-	XmlParser(const std::string &path);
-
-	/**
-	 * Read the file.
-	 */
-	void open();
-
-	/**
-	 * Handler when a tag occurs.
-	 *
-	 * @param name the tag name
-	 * @param attributes the optional attributes
-	 */
-	virtual void startElementHandler(const std::string &name,
-					 const Attrs &attributes) { }
-
-	/**
-	 * Handler when a tag ends.
-	 *
-	 * @param name the tag name
-	 */
-	virtual void endElementHandler(const std::string &name) { }
-
-	/**
-	 * Handler when data occurs. The data may have leading and
-	 * trailing spaces.
-	 *
-	 * @param data the data
-	 */
-	virtual void characterDataHandler(const std::string &data) { }
-};
-
-
-#endif // !_XML_PARSER_H_
--- a/CMakeLists.txt	Sat Nov 15 13:29:30 2014 +0100
+++ b/CMakeLists.txt	Mon Nov 17 20:29:09 2014 +0100
@@ -58,7 +58,7 @@
 option(WITH_SOCKETS "Enable sockets tests" On)
 option(WITH_TREENODE "Enable treenode tests" On)
 option(WITH_UTF8 "Enable Utf8 functions tests" On)
-option(WITH_XMLPARSER "Enable XML tests" On)
+option(WITH_XML "Enable XML tests" On)
 option(WITH_ZIP "Enable ZipArchive tests" On)
 
 if (UNIX)
@@ -109,6 +109,10 @@
 	add_subdirectory(C++/Tests/Xdg)
 endif ()
 
+if (WITH_XML)
+	add_subdirectory(C++/Tests/Xml)
+endif ()
+
 if (WITH_ZIP)
 	add_subdirectory(C++/Tests/Zip)
 endif ()