Coder Social home page Coder Social logo

tinfoilboy / ctml Goto Github PK

View Code? Open in Web Editor NEW
210.0 15.0 27.0 202 KB

A C++ HTML document constructor only depending on the standard library.

License: MIT License

C++ 99.87% CMake 0.13%
html-document cpp cpp-library generator cmake mit-license

ctml's Introduction

CTML

TravisCI Build Status AppVeyor Build Status License: MIT Github Releases

CTML is a C++ HTML document constructor, that was designed to be simple to use and implement. Has no dependencies on any other projects, only the C++ standard library.

Building

For use in a project, you may copy the ctml.hpp file into your project and include that way. Alternatively, if you use CMake, you could add CTML as a dependency to your project.

Tests

Tests are included with the library and are written using the Catch2 header-only test library. These tests are located in the tests/tests.cpp file.

Usage

Namespacing

Every class and enum in CTML is enclosed in the CTML namespace. For instance, the Node class would be under CTML::Node.

Chaining

Most methods for operating on CTML::Node instances are chainable, meaning that you may run these operations multiple times on the same expression.

Nodes

The basis of CTML is the CTML::Node class, which allows you to create simple HTML nodes and convert them to a std::string value.

The most simple valid HTML node that can be represented is, for instance, an empty paragraph tag, which can be created with the following code.

CTML::Node node("p");

Which would output in string form as:

<p></p>

To get this string output, you would use the CTML::Node::ToString(CTML::ToStringOptions) method. This method outputs a string representation of the Node and its children using the options supplied.

This ToStringOptions structure allows the user to change whether the string outputs elements using multiple lines or one line, if nodes should have a trailing new line at the end, the indentation level of the node if outputting to multiple lines, and whether text content of an element should be escaped.

You can add simple text content to this Node by changing that line to the following:

CTML::Node node("p", "Hello world!");

Which would output as the following:

<p>Hello world!</p>

You can quickly add classes and IDs to a Node (possibly attributes in the future) with a syntax in the name field that mimics Emmet Abbriviations. This is shown in the following definition:

CTML::Node node("p.text#para", "Hello world!");

Which would output the following HTML:

<p class="text" id="para">Hello world!</p>

You can then append children to these Node instances by using the CTML::Node::AppendChild(CTML::Node) method, like below:

CTML::Node node("div");

node.AppendChild(CTML::Node("p", "Hello world!"));

Which would give this output:

<div><p>Hello world!</p></div>

You can also append more text to the parent node with the CTML::Node::AppendText(std::string) method, which simply adds a Node with the type of TEXT to the children. This is shown below:

CTML::Node node("div");

node.AppendChild(CTML::Node("p", "Hello world!"))
    .AppendText("Hello again!");

Which would output as:

<div><p>Hello world!</p> Hello again!</div>

You can also set attributes on a Node, modifying the below example to do so looks like:

CTML::Node node("div");

node.SetAttribute("title", "Hello title!")
    .AppendChild(CTML::Node("p", "Hello world!"))
    .AppendText("Hello again!");

Which would output as:

<div title="Hello title!"><p>Hello world!</p> Hello again!</div>

If you wish to make a self-closing element, such as an <img> element, you will want to use the CTML::Node::UseClosingTag(bool) method. This method allows you to toggle the use of the closing tag. Keep in mind that toggling this also doesn't append any of the child nodes to the output. An example of the method is below:

CTML::Node image("img");

image.SetAttribute("src", "/animage.png")
     .UseClosingTag(false);

This has the following output:

<img src="/animage.png">

Documents

To create an HTML document that contains these nodes, you can use the CTML::Document class. This class includes doctype, head, and body nodes for adding nodes to.

A simple HTML document would be created with:

CTML::Document document;

You can then output this as a string with the CTML::Document::ToString(CTML::ToStringOptions) method. This method uses the same structure that the node class uses.

By using the default empty Document::ToString method you would get an output of:

<!DOCTYPE html><html><head></head><body></body></html>

You can then append nodes to it using the CTML::Document::AppendNodeToHead(CTML::Node) or CTML::Document::AppendNodeToBody(CTML::Node) methods.

Searching Nodes

There are two ways to search through the document tree for nodes. The first of these ways is to use the CTML::Node::GetChildByName(const std::string&) method. This will only search the node's immediate children without recursion, and only operates based on the name of the node, such as div or section. In addition for legacy reasons, it will only return a single node, even if there are multiple matches.

If you need more fine-grained searching for nodes, you can use the CTML::Node::QuerySelector(const std::string&) method, which also has an alias of CTML::Document::QuerySelector(const std::string&) for searching the entire document for matches. This method operates similarly to querySelectorAll in JavaScript, in that it will recursively search the a node's children for matches to the selector, and return only that node match.

For example, if you have the following document:

<div class="one"><div class="two"><div class="three"></div></div><div>

And you use the following code:

std::vector<Node*> matches = CTML::Document::QuerySelector(".one .two .three")

The matches vector will only have one child, a pointer to the node for div.three. This method supports searching by any combination of element name, attribute name and value (including using different attribute comparators, as can be found here), class, and ID.

License

CTML is licensed under the MIT License, the terms of which can be seen here.

ctml's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ctml's Issues

Append Text to Node

How can I append a text to a node?

For example:

CTML::Node par{"p", "Hello world "};
...
par.AppendText("text");

Or, how can I create this node?

<p>Hello world. <span>Hello</span> You</p>

const correctness

Hello I like the idea of the library and it's pretty intuitive to use but i notices a few thing about it that doesn't seem right.

#include "CTML.h"
#include <iostream>

int main ()
{
    const CTML::Document document;
    std::cout << document.ToString(CTML::MULTILINE);
}

This will fail to compile due to ToString not being a const member function. Now unless ToString mutates Document it should be const function.

I also noticed this with your get functions have the same issue.

SetContent not working

First, thank you for the clean rewrite of 1.0.0. This broke everything in my code. ;D

I have now another issue. I'm using 0b8ba53 (currently latest) commit.

While

CTML::Node tableRow{"tr"};
tableRow.AppendChild(CTML::Node("td", "my_content1"));

works, the following doesn't work anymore:

CTML::Node tableRow{"tr"};
tableRow.AppendChild(CTML::Node("td").SetContent("my_content2"));

In the first example my_content1 is the content, like expected, while in the second the call of SetContent will not set the string as content.

It's not actually a problem, since the first code does the job perfect. But maybe some legacy code will break.

Implement a lightweight HTML/XML parser

It would be a bit nicer to be able to parse a string and get a list of nodes from that string for uses such as creating nodes from a string and appending that to a document, or even loading an html file and being able to change the content of it for templating or similar.

I feel like this could be done with a simple function like

std::vector<CTML::Node> parse_nodes_to_string(const std::string& content)

Which then would just return the list of nodes that it was able to parse.

Support non-closing tags such as <meta>

There are some HTML tags, which do not have a closing tag. For example the <meta> tag below is valid:

<meta charset="UTF-8">

While CTML

CTML::Node{"meta"}
  .SetAttribute("charset", "UTF-8")
  .ToString(CTML::Readability::SINGLE_LINE)

outputs

<meta charset="UTF-8"></meta>

This is invalid HTML and parsers do not need to deal with it correctly.

Improve the library's code style.

Back when I wrote the code for this library, I had this habit where everything was mashed together and there were comments strewn all around.

I feel that this is harder to read now, and in need of change, as anyone else making changes/using the library would most likely find it's source cryptic.

Tests fail as they expect std::map providing a sorted iterator (which is doesn't)

When running the tests, one may get (at least on my machine with GCC 8.2)

No Close Test failed!
Node Output: <img alt="Hilarious image" src="funnypicture.png">
Expected Output: <img src="funnypicture.png" alt="Hilarious image">

Because of this

CTML/CTML/Node.h

Lines 99 to 101 in 0eb16c3

for (const auto& attr : m_attributes) {
elem += " " + attr.first + "=\"" + attr.second + "\"";
}

and this

CTML/CTML/Node.h

Lines 200 to 201 in 0eb16c3

if (name != "class" && name != "id")
m_attributes[name] = value;

and its usage at

CTML/CTML/tests.cpp

Lines 87 to 88 in 0eb16c3

const auto nodeString = "<img src=\"funnypicture.png\" alt=\"Hilarious image\">";
Node testNode = Node("img").SetAttribute("src", "funnypicture.png").SetAttribute("alt", "Hilarious image").UseClosingTag(false);

I'm not sure, whether someone actually wants the attributes serialized in the same order they were defined. Otherwise, the unit tests needs to be adjusted.

Implement selectors to get child nodes

Getting children from a Node currently has to be done by either iterating the children or knowing the index of the child that you wish to grab. It may be beneficial to add functionality to grab these children via the selector parsing that is already in the library for creating elements from abbreviations. This would then allow for more flexibility in modifying existing structures and closer mimic the methods used for getting elements in the browser.

Add support for <img>, <href>, and <link> HTML tags

I made new node types to support these and updated the Node() constructor and ToString() function to support them. It's not perfect and it's very quick and dirty but it added the missing functionality:

This allows images to be used as:
CTML::Node(CTML::NodeType::IMG, "", "img\\logo.png");

Node(
	const NodeType& type,
	std::string name="",
	std::string content=""
)
.
.
.
else if (type == NodeType::IMG
	|| type == NodeType::HREF
	|| type == NodeType::LINK)
{
	this->SetName(name);
	m_content = content;
}
std::string ToString(ToStringOptions options={}) const
.
.
.
else if (m_type == NodeType::IMG)
{
	output << indent << "<img " ;
	output << "src=" << m_content << ">";
}
else if (m_type == NodeType::HREF)
{
	output << indent << "<a href=" << m_content << ">" << m_name << "</a>";
}
else if (m_type == NodeType::LINK)
{
	output << indent << "<link " << m_content << "/>";
}

Misc enhancements

A few possible enhancements:

There are extra copies of Node objects in code like this:

                Node& AppendChild(Node child) {
			m_children.push_back(child);
			return *this;
		}

This would be more efficient like:

                template<class T>
                Node& AppendChild(T&& child) {
			m_children.push_back( std::move(child) );
			return *this;
		}

Likewise _CountOccurrences(std::string finder, const std::string& findable) and _ParseClassesAndIDS(std::string classesAndIDs) appear to be able to take a const ref to string to avoid a copy.

Depending on which C++ standard you are supporting, you could also accept std::string_view instead of std::string in your interface, which may avoid copies.

I also notice you have used names that are reserved for the implementation when you name things with an underscore and capital letter like _GetFormattedContent, which means it's technically undefined behavior.

Add ability to delete nodes

I want to construct an HTML doc, display it, and update it as the user does things. At present there's not way to delete nodes, only add them.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.