Initial commit

This commit is contained in:
Jacob 2024-05-05 23:31:42 +02:00
commit 0641a95770
11 changed files with 1994 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
.cache
build

83
CMakeLists.txt Normal file
View file

@ -0,0 +1,83 @@
cmake_minimum_required(VERSION 3.1...3.29)
project(docpp LANGUAGES CXX VERSION 0.0.1)
if (MSVC)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
endif()
set_property(GLOBAL PROPERTY CXX_STANDARD 17)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
add_library(${PROJECT_NAME} SHARED)
target_sources(${PROJECT_NAME} PRIVATE
"src/docpp.cpp"
)
target_include_directories(${PROJECT_NAME} PRIVATE "${PROJECT_SOURCE_DIR}")
set_target_properties(${PROJECT_NAME} PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION}
PUBLIC_HEADER "include/docpp.hpp"
)
install(TARGETS ${PROJECT_NAME}
EXPORT ${PROJECT_NAME}Targets
FILE_SET HEADERS
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin
PUBLIC_HEADER DESTINATION include/docpp
INCLUDES DESTINATION include)
set(INCLUDE_INSTALL_DIR include/)
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
"${PROJECT_NAME}ConfigVersion.cmake"
VERSION ${PROJECT_VERSION}
COMPATIBILITY AnyNewerVersion)
install(EXPORT ${PROJECT_NAME}Targets
FILE ${PROJECT_NAME}Targets.cmake
NAMESPACE docpp::
DESTINATION lib/cmake/${PROJECT_NAME})
install(FILES "${PROJECT_SOURCE_DIR}/cmake/${PROJECT_NAME}Config.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
DESTINATION lib/cmake/${PROJECT_NAME})
if (!${ENABLE_TESTS})
return()
else()
enable_testing()
endif()
find_package(Catch2 3 REQUIRED)
add_executable(${PROJECT_NAME}_test
tests/test.cpp
)
target_include_directories(${PROJECT_NAME}_test PRIVATE
"${PROJECT_SOURCE_DIR}"
)
target_link_libraries(${PROJECT_NAME}_test PRIVATE
Catch2::Catch2WithMain
)
include(CTest)
include(Catch)
catch_discover_tests(${PROJECT_NAME}_test)
add_custom_command(
TARGET ${PROJECT_NAME}_test
COMMENT "Run tests"
POST_BUILD
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --rerun-failed -C
$<CONFIGURATION> -R "^${PROJECT_NAME}_test$"
)

165
LICENSE Normal file
View file

@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

27
README.md Normal file
View file

@ -0,0 +1,27 @@
# docpp
Small C++ library for generating XML, HTML and CSS.
## Installation
To install the library, you can utilize the provided CMakeLists.txt file:
```sh
mkdir build
cd build
cmake ..
cmake --build .
cmake --install . --prefix /usr
```
## Usage
Just include docpp.hpp in your project and link against the library. Examples can be found in the examples directory.
## License
This project is licensed under the GNU Lesser General Public License v3.0 - see the [LICENSE.md](LICENSE.md) file for details.
## Code of Conduct
None. Just don't blow up my house.

304
cmake/Catch.cmake Normal file
View file

@ -0,0 +1,304 @@
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.
#[=======================================================================[.rst:
Catch
-----
This module defines a function to help use the Catch test framework.
The :command:`catch_discover_tests` discovers tests by asking the compiled test
executable to enumerate its tests. This does not require CMake to be re-run
when tests change. However, it may not work in a cross-compiling environment,
and setting test properties is less convenient.
This command is intended to replace use of :command:`add_test` to register
tests, and will create a separate CTest test for each Catch test case. Note
that this is in some cases less efficient, as common set-up and tear-down logic
cannot be shared by multiple test cases executing in the same instance.
However, it provides more fine-grained pass/fail information to CTest, which is
usually considered as more beneficial. By default, the CTest test name is the
same as the Catch name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``.
.. command:: catch_discover_tests
Automatically add tests with CTest by querying the compiled test executable
for available tests::
catch_discover_tests(target
[TEST_SPEC arg1...]
[EXTRA_ARGS arg1...]
[WORKING_DIRECTORY dir]
[TEST_PREFIX prefix]
[TEST_SUFFIX suffix]
[PROPERTIES name1 value1...]
[TEST_LIST var]
[REPORTER reporter]
[OUTPUT_DIR dir]
[OUTPUT_PREFIX prefix]
[OUTPUT_SUFFIX suffix]
[DISCOVERY_MODE <POST_BUILD|PRE_TEST>]
)
``catch_discover_tests`` sets up a post-build command on the test executable
that generates the list of tests by parsing the output from running the test
with the ``--list-test-names-only`` argument. This ensures that the full
list of tests is obtained. Since test discovery occurs at build time, it is
not necessary to re-run CMake when the list of tests changes.
However, it requires that :prop_tgt:`CROSSCOMPILING_EMULATOR` is properly set
in order to function in a cross-compiling environment.
Additionally, setting properties on tests is somewhat less convenient, since
the tests are not available at CMake time. Additional test properties may be
assigned to the set of tests as a whole using the ``PROPERTIES`` option. If
more fine-grained test control is needed, custom content may be provided
through an external CTest script using the :prop_dir:`TEST_INCLUDE_FILES`
directory property. The set of discovered tests is made accessible to such a
script via the ``<target>_TESTS`` variable.
The options are:
``target``
Specifies the Catch executable, which must be a known CMake executable
target. CMake will substitute the location of the built executable when
running the test.
``TEST_SPEC arg1...``
Specifies test cases, wildcarded test cases, tags and tag expressions to
pass to the Catch executable with the ``--list-test-names-only`` argument.
``EXTRA_ARGS arg1...``
Any extra arguments to pass on the command line to each test case.
``WORKING_DIRECTORY dir``
Specifies the directory in which to run the discovered test cases. If this
option is not provided, the current binary directory is used.
``TEST_PREFIX prefix``
Specifies a ``prefix`` to be prepended to the name of each discovered test
case. This can be useful when the same test executable is being used in
multiple calls to ``catch_discover_tests()`` but with different
``TEST_SPEC`` or ``EXTRA_ARGS``.
``TEST_SUFFIX suffix``
Similar to ``TEST_PREFIX`` except the ``suffix`` is appended to the name of
every discovered test case. Both ``TEST_PREFIX`` and ``TEST_SUFFIX`` may
be specified.
``PROPERTIES name1 value1...``
Specifies additional properties to be set on all tests discovered by this
invocation of ``catch_discover_tests``.
``TEST_LIST var``
Make the list of tests available in the variable ``var``, rather than the
default ``<target>_TESTS``. This can be useful when the same test
executable is being used in multiple calls to ``catch_discover_tests()``.
Note that this variable is only available in CTest.
``REPORTER reporter``
Use the specified reporter when running the test case. The reporter will
be passed to the Catch executable as ``--reporter reporter``.
``OUTPUT_DIR dir``
If specified, the parameter is passed along as
``--out dir/<test_name>`` to Catch executable. The actual file name is the
same as the test name. This should be used instead of
``EXTRA_ARGS --out foo`` to avoid race conditions writing the result output
when using parallel test execution.
``OUTPUT_PREFIX prefix``
May be used in conjunction with ``OUTPUT_DIR``.
If specified, ``prefix`` is added to each output file name, like so
``--out dir/prefix<test_name>``.
``OUTPUT_SUFFIX suffix``
May be used in conjunction with ``OUTPUT_DIR``.
If specified, ``suffix`` is added to each output file name, like so
``--out dir/<test_name>suffix``. This can be used to add a file extension to
the output e.g. ".xml".
``DL_PATHS path...``
Specifies paths that need to be set for the dynamic linker to find shared
libraries/DLLs when running the test executable (PATH/LD_LIBRARY_PATH respectively).
These paths will both be set when retrieving the list of test cases from the
test executable and when the tests are executed themselves. This requires
cmake/ctest >= 3.22.
`DISCOVERY_MODE mode``
Provides control over when ``catch_discover_tests`` performs test discovery.
By default, ``POST_BUILD`` sets up a post-build command to perform test discovery
at build time. In certain scenarios, like cross-compiling, this ``POST_BUILD``
behavior is not desirable. By contrast, ``PRE_TEST`` delays test discovery until
just prior to test execution. This way test discovery occurs in the target environment
where the test has a better chance at finding appropriate runtime dependencies.
``DISCOVERY_MODE`` defaults to the value of the
``CMAKE_CATCH_DISCOVER_TESTS_DISCOVERY_MODE`` variable if it is not passed when
calling ``catch_discover_tests``. This provides a mechanism for globally selecting
a preferred test discovery behavior without having to modify each call site.
#]=======================================================================]
#------------------------------------------------------------------------------
function(catch_discover_tests TARGET)
cmake_parse_arguments(
""
""
"TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST;REPORTER;OUTPUT_DIR;OUTPUT_PREFIX;OUTPUT_SUFFIX;DISCOVERY_MODE"
"TEST_SPEC;EXTRA_ARGS;PROPERTIES;DL_PATHS"
${ARGN}
)
if(NOT _WORKING_DIRECTORY)
set(_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
endif()
if(NOT _TEST_LIST)
set(_TEST_LIST ${TARGET}_TESTS)
endif()
if (_DL_PATHS)
if(${CMAKE_VERSION} VERSION_LESS "3.22.0")
message(FATAL_ERROR "The DL_PATHS option requires at least cmake 3.22")
endif()
endif()
if(NOT _DISCOVERY_MODE)
if(NOT CMAKE_CATCH_DISCOVER_TESTS_DISCOVERY_MODE)
set(CMAKE_CATCH_DISCOVER_TESTS_DISCOVERY_MODE "POST_BUILD")
endif()
set(_DISCOVERY_MODE ${CMAKE_CATCH_DISCOVER_TESTS_DISCOVERY_MODE})
endif()
if (NOT _DISCOVERY_MODE MATCHES "^(POST_BUILD|PRE_TEST)$")
message(FATAL_ERROR "Unknown DISCOVERY_MODE: ${_DISCOVERY_MODE}")
endif()
## Generate a unique name based on the extra arguments
string(SHA1 args_hash "${_TEST_SPEC} ${_EXTRA_ARGS} ${_REPORTER} ${_OUTPUT_DIR} ${_OUTPUT_PREFIX} ${_OUTPUT_SUFFIX}")
string(SUBSTRING ${args_hash} 0 7 args_hash)
# Define rule to generate test list for aforementioned test executable
set(ctest_file_base "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}-${args_hash}")
set(ctest_include_file "${ctest_file_base}_include.cmake")
set(ctest_tests_file "${ctest_file_base}_tests.cmake")
get_property(crosscompiling_emulator
TARGET ${TARGET}
PROPERTY CROSSCOMPILING_EMULATOR
)
if(_DISCOVERY_MODE STREQUAL "POST_BUILD")
add_custom_command(
TARGET ${TARGET} POST_BUILD
BYPRODUCTS "${ctest_tests_file}"
COMMAND "${CMAKE_COMMAND}"
-D "TEST_TARGET=${TARGET}"
-D "TEST_EXECUTABLE=$<TARGET_FILE:${TARGET}>"
-D "TEST_EXECUTOR=${crosscompiling_emulator}"
-D "TEST_WORKING_DIR=${_WORKING_DIRECTORY}"
-D "TEST_SPEC=${_TEST_SPEC}"
-D "TEST_EXTRA_ARGS=${_EXTRA_ARGS}"
-D "TEST_PROPERTIES=${_PROPERTIES}"
-D "TEST_PREFIX=${_TEST_PREFIX}"
-D "TEST_SUFFIX=${_TEST_SUFFIX}"
-D "TEST_LIST=${_TEST_LIST}"
-D "TEST_REPORTER=${_REPORTER}"
-D "TEST_OUTPUT_DIR=${_OUTPUT_DIR}"
-D "TEST_OUTPUT_PREFIX=${_OUTPUT_PREFIX}"
-D "TEST_OUTPUT_SUFFIX=${_OUTPUT_SUFFIX}"
-D "TEST_DL_PATHS=${_DL_PATHS}"
-D "CTEST_FILE=${ctest_tests_file}"
-P "${_CATCH_DISCOVER_TESTS_SCRIPT}"
VERBATIM
)
file(WRITE "${ctest_include_file}"
"if(EXISTS \"${ctest_tests_file}\")\n"
" include(\"${ctest_tests_file}\")\n"
"else()\n"
" add_test(${TARGET}_NOT_BUILT-${args_hash} ${TARGET}_NOT_BUILT-${args_hash})\n"
"endif()\n"
)
elseif(_DISCOVERY_MODE STREQUAL "PRE_TEST")
get_property(GENERATOR_IS_MULTI_CONFIG GLOBAL
PROPERTY GENERATOR_IS_MULTI_CONFIG
)
if(GENERATOR_IS_MULTI_CONFIG)
set(ctest_tests_file "${ctest_file_base}_tests-$<CONFIG>.cmake")
endif()
string(CONCAT ctest_include_content
"if(EXISTS \"$<TARGET_FILE:${TARGET}>\")" "\n"
" if(NOT EXISTS \"${ctest_tests_file}\" OR" "\n"
" NOT \"${ctest_tests_file}\" IS_NEWER_THAN \"$<TARGET_FILE:${TARGET}>\" OR\n"
" NOT \"${ctest_tests_file}\" IS_NEWER_THAN \"\${CMAKE_CURRENT_LIST_FILE}\")\n"
" include(\"${_CATCH_DISCOVER_TESTS_SCRIPT}\")" "\n"
" catch_discover_tests_impl(" "\n"
" TEST_EXECUTABLE" " [==[" "$<TARGET_FILE:${TARGET}>" "]==]" "\n"
" TEST_EXECUTOR" " [==[" "${crosscompiling_emulator}" "]==]" "\n"
" TEST_WORKING_DIR" " [==[" "${_WORKING_DIRECTORY}" "]==]" "\n"
" TEST_SPEC" " [==[" "${_TEST_SPEC}" "]==]" "\n"
" TEST_EXTRA_ARGS" " [==[" "${_EXTRA_ARGS}" "]==]" "\n"
" TEST_PROPERTIES" " [==[" "${_PROPERTIES}" "]==]" "\n"
" TEST_PREFIX" " [==[" "${_TEST_PREFIX}" "]==]" "\n"
" TEST_SUFFIX" " [==[" "${_TEST_SUFFIX}" "]==]" "\n"
" TEST_LIST" " [==[" "${_TEST_LIST}" "]==]" "\n"
" TEST_REPORTER" " [==[" "${_REPORTER}" "]==]" "\n"
" TEST_OUTPUT_DIR" " [==[" "${_OUTPUT_DIR}" "]==]" "\n"
" TEST_OUTPUT_PREFIX" " [==[" "${_OUTPUT_PREFIX}" "]==]" "\n"
" TEST_OUTPUT_SUFFIX" " [==[" "${_OUTPUT_SUFFIX}" "]==]" "\n"
" CTEST_FILE" " [==[" "${ctest_tests_file}" "]==]" "\n"
" TEST_DL_PATHS" " [==[" "${_DL_PATHS}" "]==]" "\n"
" CTEST_FILE" " [==[" "${CTEST_FILE}" "]==]" "\n"
" )" "\n"
" endif()" "\n"
" include(\"${ctest_tests_file}\")" "\n"
"else()" "\n"
" add_test(${TARGET}_NOT_BUILT ${TARGET}_NOT_BUILT)" "\n"
"endif()" "\n"
)
if(GENERATOR_IS_MULTI_CONFIG)
foreach(_config ${CMAKE_CONFIGURATION_TYPES})
file(GENERATE OUTPUT "${ctest_file_base}_include-${_config}.cmake" CONTENT "${ctest_include_content}" CONDITION $<CONFIG:${_config}>)
endforeach()
string(CONCAT ctest_include_multi_content
"if(NOT CTEST_CONFIGURATION_TYPE)" "\n"
" message(\"No configuration for testing specified, use '-C <cfg>'.\")" "\n"
"else()" "\n"
" include(\"${ctest_file_base}_include-\${CTEST_CONFIGURATION_TYPE}.cmake\")" "\n"
"endif()" "\n"
)
file(GENERATE OUTPUT "${ctest_include_file}" CONTENT "${ctest_include_multi_content}")
else()
file(GENERATE OUTPUT "${ctest_file_base}_include.cmake" CONTENT "${ctest_include_content}")
file(WRITE "${ctest_include_file}" "include(\"${ctest_file_base}_include.cmake\")")
endif()
endif()
if(NOT ${CMAKE_VERSION} VERSION_LESS "3.10.0")
# Add discovered tests to directory TEST_INCLUDE_FILES
set_property(DIRECTORY
APPEND PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}"
)
else()
# Add discovered tests as directory TEST_INCLUDE_FILE if possible
get_property(test_include_file_set DIRECTORY PROPERTY TEST_INCLUDE_FILE SET)
if (NOT ${test_include_file_set})
set_property(DIRECTORY
PROPERTY TEST_INCLUDE_FILE "${ctest_include_file}"
)
else()
message(FATAL_ERROR "Cannot set more than one TEST_INCLUDE_FILE")
endif()
endif()
endfunction()
###############################################################################
set(_CATCH_DISCOVER_TESTS_SCRIPT
${CMAKE_CURRENT_LIST_DIR}/CatchAddTests.cmake
CACHE INTERNAL "Catch2 full path to CatchAddTests.cmake helper file"
)

2
cmake/docppConfig.cmake Normal file
View file

@ -0,0 +1,2 @@
include(CMakeFindDependencyMacro)
include(${CMAKE_CURRENT_LIST_DIR}/docppTargets.cmake)

1
compile_commands.json Symbolic link
View file

@ -0,0 +1 @@
build/compile_commands.json

116
examples/hello-world.cpp Normal file
View file

@ -0,0 +1,116 @@
// Compile with: g++ hello-world.cpp -o hello-world -ldocpp
#include <iostream>
#include <fstream>
#include <docpp/docpp.hpp>
int main() {
/* This is your root document. It can hold *one* HTML section, and that section can hold any number of elements and/or sections.
* By default, the root document will prepend a doctype declaration. If you don't want that (e.g., if you're writing XML), you can
* use docpp::HTML::HTMLDocument::setDocType() to set the doctype to your preferred value.
*
* To get the document as an std::string object, call doc.get().
*/
docpp::HTML::HTMLDocument doc{};
/* This is an HTML section. It can hold any number of elements and/or sections.
* The first argument is the type of section, and this can either be a predefined value (e.g., docpp::HTML::SECTION_HTML) or a
* custom value in the form of an std::string object.
*
* The second argument is an HTMLElementProperties object, which is a collection of HTMLProperty objects. Each property is a std::pair of an
* attribute name and an attribute value. If you don't want to specify any attributes, you can pass an empty HTMLElementAttributes object.
* If you need to change the tag and/or attributes later, you can use the set() method.
*
* Note that it is very important that you do not append a section or element to a section until you have finished its construction,
* because push_back() makes a copy of the object you pass to it. If you need to make changes later, you can use methods such as find() and swap(),
* or construct a new object.
*
* To get the section as an std::string object, call section.get().
*/
docpp::HTML::HTMLSection htmlSection(docpp::HTML::SECTION_HTML, {}); // <html></html>
docpp::HTML::HTMLSection headSection(docpp::HTML::SECTION_HEAD, {}); // <head></head>
docpp::HTML::HTMLSection bodySection(docpp::HTML::SECTION_BODY, {}); // <body></body>
docpp::HTML::HTMLSection footerSection(docpp::HTML::SECTION_FOOTER, {}); // <footer></footer>
/* This is an HTML element. Unlike a section, an element cannot hold any other elements or sections, rather it holds text and/or attributes.
* The first argument is the type of element, and this should simply be the tag name (e.g., "p", "h1", "a", etc.).
*
* The second argument is an HTMLElementProperties object, which is a collection of HTMLProperty objects. Each property is a std::pair of an
* attribute name and an attribute value. If you don't want to specify any attributes, you can pass an empty HTMLElementAttributes object.
* If you need to change the element's tag, attributes, type or text later, you can use the set() method.
*
* The third argument is the text content of the element. If you don't want to specify any text, you can pass an empty std::string object.
*
* The fourth argument is an integer representing the closing tag type. This can be one of the following:
*
* - docpp::HTML::TYPE_NON_CLOSED: No closing tag will be appended.
* Example: <img src="example.jpg">
* - docpp::HTML::TYPE_NON_SELF_CLOSING: A closing tag will be appended.
* Example: <p>Hello world</p>
* - docpp::HTML::TYPE_SELF_CLOSING: A self-closing tag will be appended.
* Example: <img src="example.jpg">
*
* To get the element as an std::string object, call element.get().
*/
docpp::HTML::HTMLElement titleElement("title", {}, "Hello world document"); // <title>Hello world document</title>
/* Add the title and meta elements to the head section. */
headSection.push_back(titleElement);
headSection.push_back(docpp::HTML::HTMLElement("meta", {{docpp::HTML::HTMLProperty("name", "description"), docpp::HTML::HTMLProperty("content", "Hello world document description!")}}, "", docpp::HTML::TYPE_NON_CLOSED));
/* This is a CSS document. It is essentially the CSS equivalent of an HTML section.
* It is essentially a collection of CSSElement objects, which is a collection of CSSProperty objects.
*
* In other words, a CSS document is a collection of CSS elements, which are collections of CSS properties.
*/
docpp::CSS::CSSStylesheet stylesheet{};
/* This is a CSS element. It is essentially a collection of CSS properties.
* The first argument is the type of element, and this should simply be the tag name (e.g., "body", "p", "h1", etc.).
*
* The second argument is a CSSProperties object, which is a collection of CSSProperty objects. Each property is a std::pair of a
* property name and a property value. If you don't want to specify any properties, you can pass an empty CSSProperties object, but... of course, that's not very useful.
*
* To get the element as an std::string object, call element.get().
*/
docpp::CSS::CSSElement bodyStyling("body", {{docpp::CSS::CSSProperty("background-color", "black"), docpp::CSS::CSSProperty("color", "white")}}); // body { background-color: black; color: white; }
/* Now, let's add the body style to the stylesheet. */
stylesheet.push_back(bodyStyling);
/* To get the stylesheet as an std::string object, call stylesheet.get(). It can then be used in an HTMLElement object. */
const std::string& css = stylesheet.get(); // body { background-color: black; color: white; }
headSection.push_back(docpp::HTML::HTMLElement("style", {}, css)); // <style>body { background-color: black; color: white; }</style>
/* Add a paragraph element to the body section. */
bodySection.push_back(docpp::HTML::HTMLElement("p", {}, "Hello, world!")); // <p>Hello, world!</p>
/* Likewise, add a paragraph element to the footer section. */
footerSection.push_back(docpp::HTML::HTMLElement("p", {}, "This is the footer.")); // <p>This is the footer.</p>
/* Now, let's add the header, body and footer section to the html section.
* The order does matter, because an identifier is used internally. You can get this identifier by e.g. using find().
*/
htmlSection.push_back(headSection); // <html><head>...</head></html>
htmlSection.push_back(bodySection); // <html><head>...</head><body>...</body></html>
htmlSection.push_back(footerSection); // <html><head>...</head><body>...</body><footer>...</footer></html>
/* Now, let's add the html section to the document. */
doc.set(htmlSection);
/* Finally, let's output the document to a file and print it to standard output. */
std::ofstream file("hello-world.html");
/* Optionally, you can use the get() method with the docpp::HTML::FORMATTING_PRETTY argument to get a *slightly* more readable document.
* It still doesn't look hand-made, but it's readable at least. The same goes for the CSS document.
*/
file << doc.get(docpp::HTML::FORMATTING_PRETTY);
file.close();
std::cout << doc.get() << "\n";
/* And we're done! */
return 0;
}

556
include/docpp.hpp Normal file
View file

@ -0,0 +1,556 @@
/**
* docpp - Small C++ library for generating XML, HTML and CSS.
* Licensed under the LGPL-3.0-or-later license.
*
* Author: speedie <speedie@speedie.site>
*
* @file docpp.hpp
* @brief Header file for docpp
* @author speedie
* @date 2024
* @copyright GNU Lesser General Public License 3.0
* @version 0.0.1
*/
#pragma once
#include <string>
#include <vector>
#include <unordered_map>
/**
* @brief A namespace to represent HTML elements and documents
*/
namespace docpp {
namespace HTML {
enum {
SECTION_HTML,
SECTION_HEAD,
SECTION_BODY,
SECTION_FOOTER,
SECTION_DIV,
TYPE_SELF_CLOSING,
TYPE_NON_SELF_CLOSING,
TYPE_NON_CLOSED,
FORMATTING_NONE,
FORMATTING_PRETTY,
};
/**
* @brief A class to represent an HTML property
*/
class HTMLProperty {
private:
std::pair<std::string, std::string> property{};
protected:
public:
/**
* @brief Construct a new HTMLProperty object
* @param key The key of the property
* @param value The value of the property
*/
HTMLProperty(const std::string& key, const std::string& value);
/**
* @brief Construct a new HTMLProperty object
* @param property The property to set
*/
HTMLProperty(const std::pair<std::string, std::string>& property);
HTMLProperty() = default;
/**
* @brief Get the key of the property
* @return std::string The key of the property
*/
std::string getKey() const;
/**
* @brief Get the value of the property
* @return std::string The value of the property
*/
std::string getValue() const;
/**
* @brief Get the property.
* @return std::pair<std::string, std::string> The value of the property
*/
std::pair<std::string, std::string> get() const;
/**
* @brief Set the key of the property.
* @param key The key.
*/
void setKey(const std::string& key);
/**
* @brief Set the value of the property.
* @param value The value.
*/
void setValue(const std::string& value);
/**
* @brief Set the property
* @param property The property.
*/
void set(const std::pair<std::string, std::string>& property);
};
/**
* @brief A class to represent the properties of an HTML element
*/
class HTMLElementProperties {
private:
std::vector<HTMLProperty> properties{};
protected:
public:
/**
* @brief Get the properties of the element
* @return std::vector<HTMLProperty> The properties of the element
*/
std::vector<HTMLProperty> get() const;
/**
* @brief Set the properties of the element
* @param properties The properties to set
*/
void set(const std::vector<HTMLProperty>& properties);
/**
* @brief Add a property to the element
* @param property The property to add
*/
void push_back(const HTMLProperty& property);
/**
* @brief Construct a new HTMLElementProperties object
* @param properties The properties to set
*/
HTMLElementProperties(const std::vector<HTMLProperty>& properties);
/**
* @brief Construct a new HTMLElementProperties object
* @param property The property to add
*/
HTMLElementProperties(const HTMLProperty& property);
/**
* @brief Construct a new HTMLElementProperties object
*/
HTMLElementProperties() = default;
};
/**
* @brief A class to represent an HTML element
*/
class HTMLElement {
private:
std::string tag{};
std::string data{};
int type{TYPE_NON_SELF_CLOSING};
HTMLElementProperties properties{};
protected:
public:
/**
* @brief Construct a new HTMLElement object
* @param tag The tag of the element
* @param properties The properties of the element
* @param data The data of the element
*/
HTMLElement(const std::string& tag, const HTMLElementProperties& properties = {}, const std::string& data = {}, const int type = TYPE_NON_SELF_CLOSING);
/**
* @brief Construct a new HTMLElement object
*/
HTMLElement() = default;
/**
* @brief Set the tag, id, and classes of the element
* @param tag The tag of the element
* @param id The id of the element
* @param classes The classes of the element
*/
void set(const std::string& tag, const HTMLElementProperties& properties = {}, const std::string& data = {}, const int type = TYPE_NON_SELF_CLOSING);
/**
* @brief Get the element in the form of an HTML tag.
* @return std::string The tag of the element
*/
std::string get(const int formatting = FORMATTING_NONE) const;
/**
* @brief Get the tag of the element
* @return std::string The data of the element
*/
std::string getTag() const;
/**
* @brief Get the data of the element
* @return std::string The data of the element
*/
std::string getData() const;
};
/**
* @brief A class to represent an HTML section (head, body, etc.)
*/
class HTMLSection {
private:
int index{};
std::string tag{};
HTMLElementProperties properties{};
std::unordered_map<int, HTMLElement> elements{};
std::unordered_map<int, HTMLSection> sections{};
protected:
public:
/**
* @brief Prepend an element to the section
* @param element The element to add
*/
void push_front(const HTMLElement& element);
/**
* @brief Prepend a section to the section
* @param section The section to add
*/
void push_front(const HTMLSection& section);
/**
* @brief Append an element to the section
* @param element The element to add
*/
void push_back(const HTMLElement& element);
/**
* @brief Append a section to the section
* @param section The section to add
*/
void push_back(const HTMLSection& section);
/**
* @brief Erase an element from the section. Note that this will NOT change the size/index.
* @param index The index of the element to erase
*/
void erase(const int index);
/**
* @brief Erase a section from the section, by reading from a section. The section will be erased if it's identical to section. Note that this will NOT change the size/index.
* @param section The section to erase
*/
void erase(const HTMLSection& section);
/**
* @brief Erase an element from the section, by reading from an element. The element will be erased if it's identical to element. Note that this will NOT change the size/index.
* @param element The element to erase
*/
void erase(const HTMLElement& element);
/**
* @brief Find an element in the section
* @param element The element to find
* @return int The index of the element
*/
int find(const HTMLElement& element);
/**
* @brief Find a section in the section
* @param section The section to find
* @return int The index of the section
*/
int find(const HTMLSection& section);
/**
* @brief Insert an element into the section
* @param index The index to insert the element
* @param element The element to insert
*/
void insert(const int index, const HTMLElement& element);
/**
* @brief Insert a section into the section
* @param index The index to insert the section
* @param section The section to insert
*/
void insert(const int index, const HTMLSection& section);
/**
* @brief Get the size of the section
* @return int The size of the section
*/
int size() const;
/**
* @brief Construct a new HTMLSection object
* @param tag The tag of the section
* @param properties The properties of the section
*/
HTMLSection(const std::string& tag, const HTMLElementProperties& properties = {});
/**
* @brief Construct a new HTMLSection object
* @param tag The tag of the section
* @param properties The properties of the section
*/
HTMLSection(const int tag, const HTMLElementProperties& properties = {});
/**
* @brief Construct a new HTMLSection object
*/
HTMLSection() = default;
/**
* @brief Set the tag, id, and classes of the section
* @param tag The tag of the section
* @param id The id of the section
* @param classes The classes of the section
*/
void set(const std::string& tag, const HTMLElementProperties& properties = {});
/**
* @brief Set the tag, id, and classes of the section
* @param tag The tag of the section
* @param id The id of the section
* @param classes The classes of the section
*/
void set(const int tag, const HTMLElementProperties& properties = {});
/**
* @brief Swap two elements in the section
* @param index1 The index of the first element
* @param index2 The index of the second element
*/
void swap(const int index1, const int index2);
/**
* @brief Swap two elements in the section
* @param element1 The first element
* @param element2 The second element
*/
void swap(const HTMLElement& element1, const HTMLElement& element2);
/**
* @brief Swap two sections in the section
* @param index1 The index of the first section
* @param index2 The index of the second section
*/
void swap(const HTMLSection& section1, const HTMLSection& section2);
/**
* @brief Get the elements of the section
* @return std::vector<HTMLElement> The elements of the section
*/
std::vector<HTMLElement> getHTMLElements();
/**
* @brief Get the sections of the section
* @return std::vector<HTMLSection> The sections of the section
*/
std::vector<HTMLSection> getHTMLSections();
/**
* @brief Dump the entire section.
* @return std::string The section
*/
std::string get(const int formatting = FORMATTING_NONE) const;
};
/**
* @brief A class to represent an HTML document
*/
class HTMLDocument {
private:
std::string doctype{"<!DOCTYPE html>"};
HTMLSection document{};
protected:
public:
/**
* @brief Get the document
* @param std::string The type to return
* @return std::string The document
*/
std::string get(const int formatting = FORMATTING_NONE) const;
/**
* @brief Get the doctype of the document
* @return std::string The doctype of the document
*/
std::string getDoctype() const;
/**
* @brief Set the document
* @param document The document to set
*/
void set(const HTMLSection& document);
/**
* @brief Set the doctype of the document
* @param doctype The doctype to set
*/
void setDoctype(const std::string& doctype);
/**
* @brief Construct a new HTMLDocument object
*/
HTMLDocument() = default;
/**
* @brief Construct a new HTMLDocument object
* @param document The document to set
*/
HTMLDocument(const HTMLSection& document, const std::string& doctype = "<!DOCTYPE html>");
};
} // namespace HTML
namespace CSS {
enum {
FORMATTING_NONE,
FORMATTING_PRETTY,
};
class CSSProperty {
private:
std::pair<std::string, std::string> property{};
protected:
public:
/**
* @brief Construct a new CSSProperty object
* @param key The key of the property
* @param value The value of the property
*/
CSSProperty(const std::string& key, const std::string& value);
/**
* @brief Construct a new CSSProperty object
* @param property The property to set
*/
CSSProperty(const std::pair<std::string, std::string>& property);
CSSProperty() = default;
/**
* @brief Get the key of the property
* @return std::string The key of the property
*/
std::string getKey() const;
/**
* @brief Get the value of the property
* @return std::string The value of the property
*/
std::string getValue() const;
/**
* @brief Get the property.
* @return std::pair<std::string, std::string> The value of the property
*/
std::pair<std::string, std::string> get() const;
/**
* @brief Set the key of the property.
* @param key The key.
*/
void setKey(const std::string& key);
/**
* @brief Set the value of the property.
* @param value The value.
*/
void setValue(const std::string& value);
/**
* @brief Set the property
* @param property The property.
*/
void set(const std::pair<std::string, std::string>& property);
/**
* @brief Set the property
* @param key The key of the property
* @param value The value of the property
*/
void set(const std::string& key, const std::string& value);
};
class CSSElement {
private:
std::pair<std::string, std::vector<CSSProperty>> element{};
protected:
public:
/**
* @brief Construct a new CSSElement object
* @param tag The tag of the element
* @param properties The properties of the element
*/
CSSElement(const std::string& tag, const std::vector<CSSProperty>& properties);
/**
* @brief Construct a new CSSElement object
* @param element The element to set
*/
CSSElement(const std::pair<std::string, std::vector<CSSProperty>>& element);
CSSElement() = default;
/**
* @brief Push a property to the element
* @param property The property to push
*/
void push_back(const CSSProperty& property);
/**
* @brief Set the properties of the element
* @param properties The properties to set
*/
void set(const std::string& tag, const std::vector<CSSProperty>& properties);
/**
* @brief Set the properties of the element
* @param element The element to set
*/
void set(const std::pair<std::string, std::vector<CSSProperty>>& element);
/**
* @brief Get the element
* @return std::pair<std::string, std::vector<CSSProperty>> The element
*/
std::string get(const int formatting = FORMATTING_NONE) const;
/**
* @brief Get the tag of the element
* @return std::string The tag of the element
*/
std::string getTag() const;
/**
* @brief Get the properties of the element
* @return std::vector<CSSProperty> The properties of the element
*/
std::vector<CSSProperty> getProperties() const;
};
/**
* @brief A class to represent a CSS stylesheet
*/
class CSSStylesheet {
private:
std::vector<CSSElement> elements{};
protected:
public:
/**
* @brief Construct a new CSSStylesheet object
* @param elements The elements to set
*/
CSSStylesheet(const std::vector<CSSElement>& elements);
CSSStylesheet() = default;
/**
* @brief Prepend an element to the stylesheet
* @param element The element to add
*/
void push_front(const CSSElement& element);
/**
* @brief Append an element to the stylesheet
* @param element The element to add
*/
void push_back(const CSSElement& element);
/**
* @brief Insert an element into the stylesheet
* @param index The index to insert the element
* @param element The element to insert
*/
void insert(const int index, const CSSElement& element);
/**
* @brief Erase an element from the stylesheet. Note that this will NOT change the size/index.
* @param index The index of the element to erase
*/
void erase(const int index);
/**
* @brief Find an element in the stylesheet
* @param element The element to find
* @return int The index of the element
*/
int find(const CSSElement& element);
/**
* @brief Get the size of the stylesheet
* @return int The size of the stylesheet
*/
int size() const;
/**
* @brief Swap two elements in the stylesheet
* @param index1 The index of the first element
* @param index2 The index of the second element
*/
void swap(const int index1, const int index2);
/**
* @brief Swap two elements in the stylesheet
* @param element1 The first element
* @param element2 The second element
*/
void swap(const CSSElement& element1, const CSSElement& element2);
/**
* @brief Set the elements of the stylesheet
* @param elements The elements to set
*/
void set(const std::vector<CSSElement>& elements);
/**
* @brief Get the elements of the stylesheet
* @return std::vector<CSSElement> The elements of the stylesheet
*/
std::vector<CSSElement> getElements() const;
/**
* @brief Get the stylesheet
* @return std::string The stylesheet
*/
std::string get(const int formatting = FORMATTING_NONE) const;
};
}
}

538
src/docpp.cpp Normal file
View file

@ -0,0 +1,538 @@
/**
* docpp - Small C++ library for generating XML, HTML and CSS.
* Licensed under the LGPL-3.0-or-later license.
*
* Author: speedie <speedie@speedie.site>
*
* @file docpp.cpp
* @brief Implementation of the docpp library.
* @author speedie
* @date 2024
* @copyright GNU Lesser General Public License 3.0.
* @version 0.0.1
*/
#include <include/docpp.hpp>
#include <string>
#include <vector>
#include <unordered_map>
#include <stdexcept>
docpp::HTML::HTMLProperty::HTMLProperty(const std::string& key, const std::string& value) {
this->setKey(key);
this->setValue(value);
}
docpp::HTML::HTMLProperty::HTMLProperty(const std::pair<std::string, std::string>& property) {
this->set(property);
}
std::string docpp::HTML::HTMLProperty::getKey() const {
return this->property.first;
}
std::string docpp::HTML::HTMLProperty::getValue() const {
return this->property.second;
}
std::pair<std::string, std::string> docpp::HTML::HTMLProperty::get() const {
return this->property;
}
void docpp::HTML::HTMLProperty::setKey(const std::string& key) {
this->property.first = key;
}
void docpp::HTML::HTMLProperty::setValue(const std::string& value) {
this->property.second = value;
}
void docpp::HTML::HTMLProperty::set(const std::pair<std::string, std::string>& property) {
this->property = property;
}
docpp::HTML::HTMLElementProperties::HTMLElementProperties(const std::vector<docpp::HTML::HTMLProperty>& properties) {
this->set(properties);
}
docpp::HTML::HTMLElementProperties::HTMLElementProperties(const docpp::HTML::HTMLProperty& property) {
this->push_back(property);
}
std::vector<docpp::HTML::HTMLProperty> docpp::HTML::HTMLElementProperties::get() const {
return this->properties;
}
void docpp::HTML::HTMLElementProperties::set(const std::vector<docpp::HTML::HTMLProperty>& properties) {
this->properties = properties;
}
void docpp::HTML::HTMLElementProperties::push_back(const docpp::HTML::HTMLProperty& property) {
this->properties.push_back(property);
}
docpp::HTML::HTMLElement::HTMLElement(const std::string& tag, const HTMLElementProperties& properties, const std::string& data, const int type) {
this->set(tag, properties, data, type);
}
void docpp::HTML::HTMLElement::set(const std::string& tag, const HTMLElementProperties& properties, const std::string& data, const int type) {
this->tag = tag;
this->data = data;
this->properties = properties;
this->type = type;
}
std::string docpp::HTML::HTMLElement::get(const int formatting) const {
std::string ret{};
ret += "<" + this->tag;
for (const auto& it : this->properties.get()) {
if (!it.getKey().compare("")) continue;
if (!it.getValue().compare("")) continue;
ret += " " + it.getKey() + "=\"" + it.getValue() + "\"";
}
if (this->type != docpp::HTML::TYPE_SELF_CLOSING) {
ret += ">";
}
if (this->type == docpp::HTML::TYPE_NON_SELF_CLOSING) {
ret += this->data + "</" + this->tag + ">";
} else if (this->type == docpp::HTML::TYPE_SELF_CLOSING) {
ret += this->data + "/>";
}
if (formatting == docpp::HTML::FORMATTING_PRETTY) {
ret += "\n";
}
return std::move(ret);
}
std::string docpp::HTML::HTMLElement::getTag() const {
return this->tag;
}
std::string docpp::HTML::HTMLElement::getData() const {
return this->data;
}
docpp::HTML::HTMLSection::HTMLSection(const std::string& tag, const HTMLElementProperties& properties) {
this->tag = tag;
this->properties = properties;
}
docpp::HTML::HTMLSection::HTMLSection(const int tag, const HTMLElementProperties& properties) {
if (tag == docpp::HTML::SECTION_DIV) {
this->tag = "div";
} else if (tag == docpp::HTML::SECTION_BODY) {
this->tag = "body";
} else if (tag == docpp::HTML::SECTION_FOOTER) {
this->tag = "footer";
} else if (tag == docpp::HTML::SECTION_HEAD) {
this->tag = "head";
} else if (tag == docpp::HTML::SECTION_HTML) {
this->tag = "html";
}
this->properties = properties;
}
void docpp::HTML::HTMLSection::set(const std::string& tag, const HTMLElementProperties& properties) {
this->tag = tag;
this->properties = properties;
}
void docpp::HTML::HTMLSection::set(const int tag, const HTMLElementProperties& properties) {
if (tag == docpp::HTML::SECTION_DIV) {
this->tag = "div";
} else if (tag == docpp::HTML::SECTION_BODY) {
this->tag = "body";
} else if (tag == docpp::HTML::SECTION_FOOTER) {
this->tag = "footer";
} else if (tag == docpp::HTML::SECTION_HEAD) {
this->tag = "head";
} else if (tag == docpp::HTML::SECTION_HTML) {
this->tag = "html";
}
this->properties = properties;
}
void docpp::HTML::HTMLSection::push_front(const HTMLElement& element) {
for (int i{this->index}; i > 0; i--) {
this->elements[i] = this->elements.at(i - 1);
}
this->elements[0] = element;
this->index++;
}
void docpp::HTML::HTMLSection::push_front(const HTMLSection& section) {
for (int i{this->index}; i > 0; i--) {
this->sections.at(i) = this->sections.at(i - 1);
}
this->sections[0] = section;
this->index++;
}
void docpp::HTML::HTMLSection::push_back(const HTMLElement& element) {
this->elements[this->index++] = element;
}
void docpp::HTML::HTMLSection::push_back(const HTMLSection& section) {
this->sections[this->index++] = section;
}
void docpp::HTML::HTMLSection::erase(const int index) {
bool erased{false};
if (this->elements.find(index) != this->elements.end()) {
this->elements.erase(index);
erased = true;
} else if (this->sections.find(index) != this->sections.end()) {
this->sections.erase(index);
erased = true;
}
if (!erased) {
throw std::out_of_range("Index out of range");
}
}
void docpp::HTML::HTMLSection::erase(const HTMLSection& section) {
for (int i{0}; i < this->size(); i++) {
const auto it = this->getHTMLSections().at(i);
if (it.get() == section.get()) {
this->erase(i);
return;
}
}
throw std::out_of_range("Section not found");
}
void docpp::HTML::HTMLSection::erase(const HTMLElement& element) {
for (int i{0}; i < this->size(); i++) {
const auto it = this->getHTMLElements().at(i);
if (it.get() == element.get()) {
this->erase(i);
return;
}
}
throw std::out_of_range("Element not found");
}
void docpp::HTML::HTMLSection::insert(const int index, const HTMLElement& element) {
if (this->sections.find(index) != this->sections.end()) {
throw std::invalid_argument("Index already occupied by a section");
} else {
this->elements[index] = element;
}
this->index = std::max(this->index, index) + 1;
}
void docpp::HTML::HTMLSection::insert(const int index, const HTMLSection& section) {
this->sections[index] = section;
this->index = std::max(this->index, index) + 1;
}
int docpp::HTML::HTMLSection::find(const HTMLElement& element) {
for (int i{0}; i < this->size(); i++) {
const auto it = this->getHTMLElements().at(i);
if (it.get() == element.get()) {
return i;
}
}
throw std::out_of_range("Element not found");
}
int docpp::HTML::HTMLSection::find(const HTMLSection& section) {
for (int i{0}; i < this->size(); i++) {
const auto it = this->getHTMLSections().at(i);
if (it.get() == section.get()) {
return i;
}
}
throw std::out_of_range("Section not found");
}
int docpp::HTML::HTMLSection::size() const {
return this->index;
}
std::vector<docpp::HTML::HTMLElement> docpp::HTML::HTMLSection::getHTMLElements() {
std::vector<docpp::HTML::HTMLElement> ret{};
ret.reserve(this->index);
for (int i{0}; i < this->index; i++) {
if (this->elements.find(i) != this->elements.end()) {
ret.push_back(this->elements.at(i));
}
}
return std::move(ret);
}
std::vector<docpp::HTML::HTMLSection> docpp::HTML::HTMLSection::getHTMLSections() {
std::vector<docpp::HTML::HTMLSection> ret{};
ret.reserve(this->index);
for (int i{0}; i < this->index; i++) {
if (this->sections.find(i) != this->sections.end()) {
ret.push_back(this->sections.at(i));
}
}
return std::move(ret);
}
std::string docpp::HTML::HTMLSection::get(const int formatting) const {
std::string ret{};
ret += "<" + this->tag;
for (const auto& it : this->properties.get()) {
if (!it.getKey().compare("")) continue;
if (!it.getValue().compare("")) continue;
ret += " " + it.getKey() + "=\"" + it.getValue() + "\"";
}
ret += ">";
if (formatting == docpp::HTML::FORMATTING_PRETTY) {
ret += "\n";
}
for (int i{0}; i < this->index; i++) {
if (this->elements.find(i) != this->elements.end()) {
ret += this->elements.at(i).get(formatting);
} else if (this->sections.find(i) != this->sections.end()) {
ret += this->sections.at(i).get(formatting);
if (formatting == docpp::HTML::FORMATTING_PRETTY) {
ret += "\n";
}
}
}
ret += "</" + this->tag + ">";
return std::move(ret);
}
void docpp::HTML::HTMLSection::swap(const int index1, const int index2) {
if (this->elements.find(index1) != this->elements.end() && this->elements.find(index2) != this->elements.end()) {
std::swap(this->elements[index1], this->elements[index2]);
} else if (this->sections.find(index1) != this->sections.end() && this->sections.find(index2) != this->sections.end()) {
std::swap(this->sections[index1], this->sections[index2]);
} else {
throw std::out_of_range("Index out of range");
}
}
void docpp::HTML::HTMLSection::swap(const HTMLElement& element1, const HTMLElement& element2) {
this->swap(this->find(element1), this->find(element2));
}
void docpp::HTML::HTMLSection::swap(const HTMLSection& section1, const HTMLSection& section2) {
this->swap(this->find(section1), this->find(section2));
}
std::string docpp::HTML::HTMLDocument::get(const int formatting) const {
return this->doctype + (formatting == FORMATTING_PRETTY ? "\n" : "") + this->document.get(formatting);
}
void docpp::HTML::HTMLDocument::set(const docpp::HTML::HTMLSection& document) {
this->document = document;
}
void docpp::HTML::HTMLDocument::setDoctype(const std::string& doctype) {
this->doctype = doctype;
}
docpp::HTML::HTMLDocument::HTMLDocument(const docpp::HTML::HTMLSection& document, const std::string& doctype) {
this->set(document);
this->setDoctype(doctype);
}
std::string docpp::HTML::HTMLDocument::getDoctype() const {
return this->doctype;
}
docpp::CSS::CSSProperty::CSSProperty(const std::string& key, const std::string& value) {
this->set(key, value);
}
docpp::CSS::CSSProperty::CSSProperty(const std::pair<std::string, std::string>& property) {
this->set(property);
}
std::string docpp::CSS::CSSProperty::getKey() const {
return this->property.first;
}
std::string docpp::CSS::CSSProperty::getValue() const {
return this->property.second;
}
std::pair<std::string, std::string> docpp::CSS::CSSProperty::get() const {
return this->property;
}
void docpp::CSS::CSSProperty::setKey(const std::string& key) {
this->property.first = key;
}
void docpp::CSS::CSSProperty::setValue(const std::string& value) {
this->property.second = value;
}
void docpp::CSS::CSSProperty::set(const std::pair<std::string, std::string>& property) {
this->property = property;
}
void docpp::CSS::CSSProperty::set(const std::string& key, const std::string& value) {
this->property = std::make_pair(key, value);
}
docpp::CSS::CSSElement::CSSElement(const std::string& tag, const std::vector<CSSProperty>& properties) {
this->set(tag, properties);
}
docpp::CSS::CSSElement::CSSElement(const std::pair<std::string, std::vector<CSSProperty>>& element) {
this->set(element);
}
void docpp::CSS::CSSElement::set(const std::string& tag, const std::vector<CSSProperty>& properties) {
this->element.first = tag;
this->element.second = properties;
}
void docpp::CSS::CSSElement::set(const std::pair<std::string, std::vector<CSSProperty>>& element) {
this->element = element;
}
void docpp::CSS::CSSElement::push_back(const CSSProperty& property) {
this->element.second.push_back(property);
}
std::string docpp::CSS::CSSElement::get(const int formatting) const {
std::string ret{};
if (this->element.first.compare("")) {
ret += this->element.first + " {";
if (formatting == docpp::CSS::FORMATTING_PRETTY) {
ret += "\n";
}
for (const auto& it : this->element.second) {
if (!it.getKey().compare("")) continue;
if (!it.getValue().compare("")) continue;
ret += it.getKey() + ": " + it.getValue() + ";";
if (formatting == docpp::CSS::FORMATTING_PRETTY) {
ret += "\n";
}
}
ret += "}";
if (formatting == docpp::CSS::FORMATTING_PRETTY) {
ret += "\n";
}
}
return std::move(ret);
}
std::string docpp::CSS::CSSElement::getTag() const {
return this->element.first;
}
std::vector<docpp::CSS::CSSProperty> docpp::CSS::CSSElement::getProperties() const {
return this->element.second;
}
docpp::CSS::CSSStylesheet::CSSStylesheet(const std::vector<CSSElement>& elements) {
this->set(elements);
}
void docpp::CSS::CSSStylesheet::set(const std::vector<CSSElement>& elements) {
this->elements = elements;
}
void docpp::CSS::CSSStylesheet::push_front(const CSSElement& element) {
this->elements.insert(this->elements.begin(), element);
}
void docpp::CSS::CSSStylesheet::push_back(const CSSElement& element) {
this->elements.push_back(element);
}
void docpp::CSS::CSSStylesheet::insert(const int index, const CSSElement& element) {
if (index < 0 || index >= this->elements.size()) {
throw std::out_of_range("Index out of range");
}
this->elements.insert(this->elements.begin() + index, element);
}
void docpp::CSS::CSSStylesheet::erase(const int index) {
if (index < 0 || index >= this->elements.size()) {
throw std::out_of_range("Index out of range");
}
this->elements.erase(this->elements.begin() + index);
}
int docpp::CSS::CSSStylesheet::find(const CSSElement& element) {
for (int i{0}; i < this->elements.size(); i++) {
if (this->elements.at(i).get() == element.get()) {
return i;
}
}
throw std::out_of_range("Element not found");
}
int docpp::CSS::CSSStylesheet::size() const {
return this->elements.size();
}
void docpp::CSS::CSSStylesheet::swap(const int index1, const int index2) {
if (index1 < 0 || index1 >= this->elements.size() || index2 < 0 || index2 >= this->elements.size()) {
throw std::out_of_range("Index out of range");
}
std::swap(this->elements[index1], this->elements[index2]);
}
void docpp::CSS::CSSStylesheet::swap(const CSSElement& element1, const CSSElement& element2) {
this->swap(this->find(element1), this->find(element2));
}
std::vector<docpp::CSS::CSSElement> docpp::CSS::CSSStylesheet::getElements() const {
return this->elements;
}
std::string docpp::CSS::CSSStylesheet::get(const int formatting) const {
std::string ret{};
for (const auto& it : this->elements) {
ret += it.get(formatting);
}
return std::move(ret);
}

200
tests/test.cpp Normal file
View file

@ -0,0 +1,200 @@
#include <string>
#include <include/docpp.hpp>
#include <src/docpp.cpp>
#include <catch2/catch_test_macros.hpp>
/**
* @brief Test cases for the DuvaHTML namespace.
*/
SCENARIO("Test HTML", "[HTML]") {
auto test1 = []() {
docpp::HTML::HTMLDocument doc{};
docpp::HTML::HTMLSection html(docpp::HTML::SECTION_HTML, {});
docpp::HTML::HTMLSection head(docpp::HTML::SECTION_HEAD, {});
docpp::HTML::HTMLSection body(docpp::HTML::SECTION_BODY, {});
docpp::HTML::HTMLSection div(docpp::HTML::SECTION_DIV, {});
docpp::HTML::HTMLSection footer(docpp::HTML::SECTION_FOOTER, {});
head.push_back(docpp::HTML::HTMLElement("title", {}, "Test Title"));
body.push_back(docpp::HTML::HTMLElement("h1", {}, "Test Header"));
body.push_back(docpp::HTML::HTMLElement("p", {}, "Test Paragraph"));
docpp::HTML::HTMLElementProperties prop{};
prop.push_back(docpp::HTML::HTMLProperty(std::pair<std::string, std::string>("id", "test_id")));
body.push_back(docpp::HTML::HTMLElement("p", prop, "Test Paragraph With ID"));
div.push_back(docpp::HTML::HTMLElement("p", {}, "Test Paragraph In Div"));
body.push_back(div);
prop.push_back(docpp::HTML::HTMLProperty(std::pair<std::string, std::string>("class", "class1 class2 class3")));
body.push_back(docpp::HTML::HTMLElement("p", prop, "Test Paragraph With ID And Class"));
html.push_back(head);
html.push_back(body);
html.push_back(footer);
doc.set(html);
const std::string expected_html{"<!DOCTYPE html><html><head><title>Test Title</title></head><body><h1>Test Header</h1><p>Test Paragraph</p><p id=\"test_id\">Test Paragraph With ID</p><div><p>Test Paragraph In Div</p></div><p id=\"test_id\" class=\"class1 class2 class3\">Test Paragraph With ID And Class</p></body><footer></footer></html>"};
REQUIRE(doc.get() == expected_html);
REQUIRE(doc.get(docpp::HTML::FORMATTING_PRETTY) == "<!DOCTYPE html>\n<html>\n<head>\n<title>Test Title</title>\n</head>\n<body>\n<h1>Test Header</h1>\n<p>Test Paragraph</p>\n<p id=\"test_id\">Test Paragraph With ID</p>\n<div>\n<p>Test Paragraph In Div</p>\n</div>\n<p id=\"test_id\" class=\"class1 class2 class3\">Test Paragraph With ID And Class</p>\n</body>\n<footer>\n</footer>\n</html>");
};
auto test2 = []() {
docpp::HTML::HTMLSection section(docpp::HTML::SECTION_HTML, {});
section.push_back(docpp::HTML::HTMLElement("p", {}, "Test 1"));
section.push_back(docpp::HTML::HTMLElement("p", {}, "Test 2"));
section.push_back(docpp::HTML::HTMLElement("p", {}, "Test 3"));
section.erase(docpp::HTML::HTMLElement("p", {}, "Test 2"));
REQUIRE(section.get() == "<html><p>Test 1</p><p>Test 3</p></html>");
REQUIRE(section.get(docpp::HTML::FORMATTING_PRETTY) == "<html>\n<p>Test 1</p>\n<p>Test 3</p>\n</html>");
};
auto test3 = []() {
docpp::HTML::HTMLSection section = docpp::HTML::HTMLSection(docpp::HTML::SECTION_HTML, {});
section.push_back(docpp::HTML::HTMLElement("p", {}, "Test 1"));
section.push_back(docpp::HTML::HTMLElement("p", {}, "Test 2"));
section.push_back(docpp::HTML::HTMLElement("p", {}, "Test 3"));
int pos = section.find(docpp::HTML::HTMLElement("p", {}, "Test 2"));
section.insert(pos, docpp::HTML::HTMLElement("p", {}, "Test 2.5"));
REQUIRE(section.get() == "<html><p>Test 1</p><p>Test 2.5</p><p>Test 3</p></html>");
REQUIRE(section.get(docpp::HTML::FORMATTING_PRETTY) == "<html>\n<p>Test 1</p>\n<p>Test 2.5</p>\n<p>Test 3</p>\n</html>");
};
auto test4 = []() {
docpp::HTML::HTMLSection section = docpp::HTML::HTMLSection(docpp::HTML::SECTION_HTML, {});
section.push_back(docpp::HTML::HTMLElement("p", {}, "Test 1"));
section.push_back(docpp::HTML::HTMLElement("p", {}, "Test 2"));
section.push_back(docpp::HTML::HTMLElement("p", {}, "Test 3"));
int pos = section.find(docpp::HTML::HTMLElement("p", {}, "Test 2"));
section.erase(pos);
REQUIRE(section.get() == "<html><p>Test 1</p><p>Test 3</p></html>");
REQUIRE(section.get(docpp::HTML::FORMATTING_PRETTY) == "<html>\n<p>Test 1</p>\n<p>Test 3</p>\n</html>");
};
auto test5 = []() {
docpp::HTML::HTMLSection section = docpp::HTML::HTMLSection(docpp::HTML::SECTION_HTML, {});
docpp::HTML::HTMLSection subsection(docpp::HTML::SECTION_DIV, {});
subsection.push_back(docpp::HTML::HTMLElement("p", {}, "Test 1"));
docpp::HTML::HTMLSection subsection2(docpp::HTML::SECTION_DIV, {});
subsection2.push_back(docpp::HTML::HTMLElement("p", {}, "Test 2"));
subsection.push_back(subsection2);
section.push_back(subsection);
docpp::HTML::HTMLDocument doc{};
doc.set(section);
REQUIRE(doc.get() == "<!DOCTYPE html><html><div><p>Test 1</p><div><p>Test 2</p></div></div></html>");
REQUIRE(doc.get(docpp::HTML::FORMATTING_PRETTY) == "<!DOCTYPE html>\n<html>\n<div>\n<p>Test 1</p>\n<div>\n<p>Test 2</p>\n</div>\n</div>\n</html>");
};
auto test6 = []() {
docpp::CSS::CSSStylesheet css{};
docpp::CSS::CSSElement element{"p", {{"color", "red"}, {"font-size", "16px"}, {"font-family", "Arial"}}};
css.push_back(element);
REQUIRE(css.get() == "p {color: red;font-size: 16px;font-family: Arial;}");
REQUIRE(css.get(docpp::CSS::FORMATTING_PRETTY) == "p {\ncolor: red;\nfont-size: 16px;\nfont-family: Arial;\n}\n");
};
auto test7 = []() {
docpp::CSS::CSSStylesheet css = docpp::CSS::CSSStylesheet{};
docpp::CSS::CSSElement element = docpp::CSS::CSSElement{"p", {{"color", "red"}, {"font-size", "16px"}, {"font-family", "Arial"}}};
docpp::CSS::CSSElement element2{"div", {{"color", "blue"}, {"font-size", "12px"}, {"font-family", "Arial"}}};
css.push_back(element);
css.push_front(element2);
REQUIRE(css.get() == "div {color: blue;font-size: 12px;font-family: Arial;}p {color: red;font-size: 16px;font-family: Arial;}");
};
auto test8 = []() {
docpp::CSS::CSSStylesheet css = docpp::CSS::CSSStylesheet{};
docpp::CSS::CSSElement element = docpp::CSS::CSSElement{"p", {{"color", "red"}, {"font-size", "16px"}, {"font-family", "Arial"}}};
docpp::CSS::CSSElement element2{"div", {{"color", "blue"}, {"font-size", "12px"}, {"font-family", "Arial"}}};
css.push_back(element);
css.push_front(element2);
css.erase(css.find(element2));
REQUIRE(css.get() == "p {color: red;font-size: 16px;font-family: Arial;}");
};
auto test9 = []() {
docpp::CSS::CSSStylesheet css = docpp::CSS::CSSStylesheet{};
docpp::CSS::CSSElement element = docpp::CSS::CSSElement{"p", {{"color", "red"}, {"font-size", "16px"}, {"font-family", "Arial"}}};
docpp::CSS::CSSElement element2{"div", {{"color", "blue"}, {"font-size", "12px"}, {"font-family", "Arial"}}};
css.push_back(element);
css.push_front(element2);
css.erase(css.find(element2));
css.push_front(element2);
css.swap(css.find(element), css.find(element2));
REQUIRE(css.get() == "p {color: red;font-size: 16px;font-family: Arial;}div {color: blue;font-size: 12px;font-family: Arial;}");
};
auto test10 = []() {
docpp::HTML::HTMLSection section = docpp::HTML::HTMLSection(docpp::HTML::SECTION_HTML, {});
section.push_back(docpp::HTML::HTMLElement("p", {}, "Test 1"));
section.push_back(docpp::HTML::HTMLElement("p", {}, "Test 2"));
section.push_back(docpp::HTML::HTMLElement("p", {}, "Test 3"));
section.swap(section.find(docpp::HTML::HTMLElement("p", {}, "Test 2")), section.find(docpp::HTML::HTMLElement("p", {}, "Test 3")));
REQUIRE(section.get() == "<html><p>Test 1</p><p>Test 3</p><p>Test 2</p></html>");
};
auto test11 = []() {
docpp::HTML::HTMLSection section = docpp::HTML::HTMLSection(docpp::HTML::SECTION_HTML, {});
section.push_back(docpp::HTML::HTMLElement("p", {}, "Test 1"));
section.push_back(docpp::HTML::HTMLElement("p", {}, "Test 2"));
section.push_back(docpp::HTML::HTMLElement("p", {}, "Test 3"));
section.swap(docpp::HTML::HTMLElement("p", {}, "Test 2"), docpp::HTML::HTMLElement("p", {}, "Test 3"));
REQUIRE(section.get() == "<html><p>Test 1</p><p>Test 3</p><p>Test 2</p></html>");
};
auto test12 = []() {
docpp::HTML::HTMLSection section = docpp::HTML::HTMLSection(docpp::HTML::SECTION_HTML, {});
section.push_back(docpp::HTML::HTMLElement("p", {}, "Test 1"));
section.push_back(docpp::HTML::HTMLElement("p", {}, "Test 2"));
section.push_back(docpp::HTML::HTMLElement("p", {}, "Test 3"));
section.push_front(docpp::HTML::HTMLElement("p", {}, "Test 0"));
REQUIRE(section.get() == "<html><p>Test 0</p><p>Test 1</p><p>Test 2</p><p>Test 3</p></html>");
};
std::vector<void (*)()> tests{test1, test2, test3, test4, test5, test6, test7, test8, test9, test10, test11, test12};
for (const auto& test : tests) {
test();
}
}