Initial commit
This commit is contained in:
commit
0641a95770
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
.cache
|
||||
build
|
83
CMakeLists.txt
Normal file
83
CMakeLists.txt
Normal 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
165
LICENSE
Normal 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
27
README.md
Normal 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
304
cmake/Catch.cmake
Normal 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
2
cmake/docppConfig.cmake
Normal file
|
@ -0,0 +1,2 @@
|
|||
include(CMakeFindDependencyMacro)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/docppTargets.cmake)
|
1
compile_commands.json
Symbolic link
1
compile_commands.json
Symbolic link
|
@ -0,0 +1 @@
|
|||
build/compile_commands.json
|
116
examples/hello-world.cpp
Normal file
116
examples/hello-world.cpp
Normal 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
556
include/docpp.hpp
Normal 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
538
src/docpp.cpp
Normal 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
200
tests/test.cpp
Normal 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();
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue