Loading...
Searching...
No Matches
html.hpp
/*
SPDX-License-Identifier: MIT
Copyright © 2016-2023 Amebis
*/
#pragma once
#include "compat.hpp"
#include "exception.hpp"
#include "interval.hpp"
#include "mapping.hpp"
#include "parser.hpp"
#include "progress.hpp"
#include "sgml.hpp"
#include "string.hpp"
#include "system.hpp"
#include "unicode.hpp"
#include <exception>
#include <list>
#include <map>
#include <memory>
#include <stdexcept>
#include <vector>
#ifdef _WIN32
#undef small
#endif
namespace stdex
{
namespace html
{
template<class _Traits = std::char_traits<char>, class _Alloc = std::allocator<char>>
inline void escape(
_Inout_ std::basic_string<char, _Traits, _Alloc>& dst,
_In_reads_or_z_opt_(num_chars) const char* src, _In_ size_t num_chars = SIZE_MAX)
{
_Assume_(src || !num_chars);
for (size_t i = 0; i < num_chars && src[i]; ++i) {
switch (src[i]) {
case '&': dst += "&amp;"; break;
case ';': dst += "&semi;"; break;
case '\"': dst += "&quot;"; break;
case '\'': dst += "&#x27;"; break;
case '<': dst += "&lt;"; break;
case '>': dst += "&gt;"; break;
case 0x00a0: dst += "&nbsp;"; break; // No-break space must be escaped as SGML entity, otherwise browsers treat it as a normal space.
default: dst += src[i]; break;
}
}
}
template<class _Traits = std::char_traits<wchar_t>, class _Alloc = std::allocator<wchar_t>>
inline void escape(
_Inout_ std::basic_string<wchar_t, _Traits, _Alloc>& dst,
_In_reads_or_z_opt_(num_chars) const wchar_t* src, _In_ size_t num_chars = SIZE_MAX)
{
_Assume_(src || !num_chars);
for (size_t i = 0; i < num_chars && src[i]; ++i) {
switch (src[i]) {
case L'&': dst += L"&amp;"; break;
case L';': dst += L"&semi;"; break;
case L'\"': dst += L"&quot;"; break;
case L'\'': dst += L"&#x27;"; break;
case L'<': dst += L"&lt;"; break;
case L'>': dst += L"&gt;"; break;
case L'\u00a0': dst += L"&nbsp;"; break; // No-break space must be escaped as SGML entity, otherwise browsers treat it as a normal space.
default: dst += src[i]; break;
}
}
}
template<class _Traits = std::char_traits<char>, class _Alloc = std::allocator<char>>
inline void escape_min(_Inout_ std::basic_string<char, _Traits, _Alloc>& dst, _In_ char chr)
{
switch (chr) {
case '&': dst += "&amp;"; break;
case '<': dst += "&lt;"; break;
case '>': dst += "&gt;"; break;
case 0x00a0: dst += "&nbsp;"; break; // No-break space must be escaped as SGML entity, otherwise browsers treat it as a normal space.
default: dst += chr; break;
}
}
template<class _Traits = std::char_traits<wchar_t>, class _Alloc = std::allocator<wchar_t>>
inline void escape_min(_Inout_ std::basic_string<wchar_t, _Traits, _Alloc>& dst, _In_ wchar_t chr)
{
switch (chr) {
case L'&': dst += L"&amp;"; break;
case L'<': dst += L"&lt;"; break;
case L'>': dst += L"&gt;"; break;
case L'\u00a0': dst += L"&nbsp;"; break; // No-break space must be escaped as SGML entity, otherwise browsers treat it as a normal space.
default: dst += chr; break;
}
}
template<class _Traits = std::char_traits<char>, class _Alloc = std::allocator<char>>
inline void escape_min(
_Inout_ std::basic_string<char, _Traits, _Alloc>& dst,
_In_reads_or_z_opt_(num_chars) const char* src, _In_ size_t num_chars = SIZE_MAX)
{
_Assume_(src || !num_chars);
for (size_t i = 0; i < num_chars && src[i]; ++i) {
switch (src[i]) {
case '&': dst += "&amp;"; break;
case '<': dst += "&lt;"; break;
case '>': dst += "&gt;"; break;
case 0x00a0: dst += "&nbsp;"; break; // No-break space must be escaped as SGML entity, otherwise browsers treat it as a normal space.
default: dst += src[i]; break;
}
}
}
template<class _Traits = std::char_traits<wchar_t>, class _Alloc = std::allocator<wchar_t>>
inline void escape_min(
_Inout_ std::basic_string<wchar_t, _Traits, _Alloc>& dst,
_In_reads_or_z_opt_(num_chars) const wchar_t* src, _In_ size_t num_chars = SIZE_MAX)
{
_Assume_(src || !num_chars);
for (size_t i = 0; i < num_chars && src[i]; ++i) {
switch (src[i]) {
case L'&': dst += L"&amp;"; break;
case L'<': dst += L"&lt;"; break;
case L'>': dst += L"&gt;"; break;
case L'\u00a0': dst += L"&nbsp;"; break; // No-break space must be escaped as SGML entity, otherwise browsers treat it as a normal space.
default: dst += src[i]; break;
}
}
}
template<class _Traits = std::char_traits<char>, class _Alloc = std::allocator<char>>
inline void url_unescape(
_Inout_ std::basic_string<char, _Traits, _Alloc>& dst,
_In_reads_or_z_opt_(num_chars) const char* src, _In_ size_t num_chars = SIZE_MAX)
{
_Assume_(src || !num_chars);
for (size_t i = 0; i < num_chars && src[i];) {
switch (src[i]) {
case '+':
dst += ' '; i++;
break;
case '%': {
i++;
uint8_t chr;
if ('0' <= src[i] && src[i] <= '9') chr = (src[i++] - '0') << 4;
else if ('A' <= src[i] && src[i] <= 'F') chr = (src[i++] - 'A' + 10) << 4;
else if ('a' <= src[i] && src[i] <= 'f') chr = (src[i++] - 'a' + 10) << 4;
else { dst += '%'; continue; }
if ('0' <= src[i] && src[i] <= '9') chr |= (src[i++] - '0');
else if ('A' <= src[i] && src[i] <= 'F') chr |= (src[i++] - 'A' + 10);
else if ('a' <= src[i] && src[i] <= 'f') chr |= (src[i++] - 'a' + 10);
else { dst += '%'; dst += src[i - 1]; continue; }
dst += static_cast<char>(chr);
break;
}
default:
dst += src[i++];
}
}
}
template<class _Traits = std::char_traits<char>, class _Alloc = std::allocator<char>>
inline void url_escape(
_Inout_ std::basic_string<char, _Traits, _Alloc>& dst,
_In_reads_or_z_opt_(num_chars) const char* src, _In_ size_t num_chars = SIZE_MAX)
{
_Assume_(src || !num_chars);
for (size_t i = 0; i < num_chars && src[i]; ++i) {
switch (src[i]) {
case ' ': dst += "+"; break;
case '<': dst += "%3C"; break;
case '>': dst += "%3E"; break;
case '#': dst += "%23"; break;
case '%': dst += "%25"; break;
case '{': dst += "%7B"; break;
case '}': dst += "%7D"; break;
case '|': dst += "%7C"; break;
case '\\': dst += "%5C"; break;
case '^': dst += "%5E"; break;
case '~': dst += "%7E"; break;
case '[': dst += "%5B"; break;
case ']': dst += "%5D"; break;
case '`': dst += "%60"; break;
case ';': dst += "%3B"; break;
case '/': dst += "%2F"; break;
case '?': dst += "%3F"; break;
case ':': dst += "%3A"; break;
case '@': dst += "%40"; break;
case '=': dst += "%3D"; break;
case '&': dst += "%26"; break;
case '$': dst += "%24"; break;
default:
if (0x20 < static_cast<uint8_t>(src[i]) && static_cast<uint8_t>(src[i]) < 0x7f)
dst += src[i];
else {
dst += '%';
uint8_t n = (static_cast<uint8_t>(src[i]) & 0xf0) >> 4;
dst += n < 10 ? static_cast<char>('0' + n) : static_cast<char>('A' + n - 10);
n = ((uint8_t)src[i] & 0x0f);
dst += n < 10 ? static_cast<char>('0' + n) : static_cast<char>('A' + n - 10);
}
}
}
}
template<class _Elem, class _Traits = std::char_traits<_Elem>, class _Alloc = std::allocator<_Elem>>
inline void css_unescape(
_Inout_ std::basic_string<_Elem, _Traits, _Alloc>& dst,
_In_reads_or_z_opt_(num_chars) const _Elem* src, _In_ size_t num_chars = SIZE_MAX)
{
_Assume_(src || !num_chars);
for (size_t i = 0; i < num_chars && src[i];) {
if (src[i] != '\\')
dst += src[i++];
else if (i + 1 < num_chars) {
i++;
switch (src[i]) {
// Classic escapes
case 'n': dst += '\n'; i++; break;
case 'r': dst += '\r'; i++; break;
case 't': dst += '\t'; i++; break;
// `\` at the end of the line
case '\n': i++; break;
// `\nnnn` escape
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case 'A': case 'a':
case 'B': case 'b':
case 'C': case 'c':
case 'D': case 'd':
case 'E': case 'e':
case 'F': case 'f': {
wchar_t chr = 0;
size_t end = std::min(num_chars, i + 6);
for (; i < end; ++i) {
if ('0' <= src[i] && src[i] <= '9') chr = chr * 0x10 + src[i] - '0';
else if ('A' <= src[i] && src[i] <= 'F') chr = chr * 0x10 + src[i] - 'A' + 10;
else if ('a' <= src[i] && src[i] <= 'f') chr = chr * 0x10 + src[i] - 'a' + 10;
else break;
}
dst += static_cast<_Elem>(chr);
if (i < end && src[i] == ' ') {
// Skip space after `\nnnn`.
i++;
}
break;
}
default: dst += src[i++];
}
}
}
}
template<class _Traits = std::char_traits<char>, class _Alloc = std::allocator<char>>
inline void css_escape(
_Inout_ std::basic_string<char, _Traits, _Alloc>& dst,
_In_reads_or_z_opt_(num_chars) const char* src, _In_ size_t num_chars = SIZE_MAX)
{
_Assume_(src || !num_chars);
for (size_t i = 0; i < num_chars && src[i]; ++i) {
switch (src[i]) {
case '\\': dst += "\\\\"; break;
case '\n': dst += "\\n"; break;
case '\r': dst += "\\r"; break;
case '\t': dst += "\\t"; break;
case '\"': dst += "\\\""; break;
case '\'': dst += "\\'"; break;
default: dst += src[i]; break;
}
}
}
template<class _Traits = std::char_traits<wchar_t>, class _Alloc = std::allocator<wchar_t>>
inline void css_escape(
_Inout_ std::basic_string<wchar_t, _Traits, _Alloc>& dst,
_In_reads_or_z_opt_(num_chars) const wchar_t* src, _In_ size_t num_chars = SIZE_MAX)
{
_Assume_(src || !num_chars);
for (size_t i = 0; i < num_chars && src[i]; ++i) {
switch (src[i]) {
case L'\\': dst += L"\\\\"; break;
case L'\n': dst += L"\\n"; break;
case L'\r': dst += L"\\r"; break;
case L'\t': dst += L"\\t"; break;
case L'\"': dst += L"\\\""; break;
case L'\'': dst += L"\\'"; break;
default: dst += src[i]; break;
}
}
}
enum class element_t {
empty = 0,
a,
abbr,
acronym,
address,
applet,
area,
b,
base,
basefont,
bdo,
bgsound, // Microsoft Specific
big,
blink, // Microsoft Specific
blockquote,
body,
br,
button,
caption,
center,
cite,
code,
col,
colgroup,
comment, // Microsoft Specific
dd,
del,
dfn,
dir,
div,
dl,
dt,
em,
embed, // Microsoft Specific
fieldset,
font,
form,
frame,
frameset,
h1,
h2,
h3,
h4,
h5,
h6,
head,
hr,
html,
i,
iframe,
img,
input,
ins,
isindex,
kbd,
label,
legend,
li,
link,
listing, // Microsoft Specific
map,
marquee, // Microsoft Specific
menu,
meta,
nextid, // Microsoft Specific
nobr, // Microsoft Specific
noembed, // Microsoft Specific
noframes,
noscript,
object,
ol,
optgroup,
option,
p,
param,
plaintext, // Microsoft Specific
pre,
q,
rt, // Microsoft Specific
ruby, // Microsoft Specific
s,
samp,
script,
select,
small,
span,
strike,
strong,
style,
sub,
sup,
table,
tbody,
td,
textarea,
tfoot,
th,
thead,
title,
tr,
tt,
u,
ul,
var,
wbr, // Microsoft Specific
xmp, // Microsoft Specific
unknown = -1,
PCDATA = -2,
CDATA = -3,
};
enum class element_span_t {
needs_end = 0,
end_optional,
immediate,
};
struct element_traits
{
static inline element_span_t span(_In_ element_t code)
{
static element_span_t lookup[] = {
element_span_t::needs_end, // a
element_span_t::needs_end, // abbr
element_span_t::needs_end, // acronym
element_span_t::needs_end, // address
element_span_t::needs_end, // applet
element_span_t::immediate, // area
element_span_t::needs_end, // b
element_span_t::immediate, // base
element_span_t::immediate, // basefont
element_span_t::needs_end, // bdo
element_span_t::immediate, // bgsound
element_span_t::needs_end, // big
element_span_t::needs_end, // blink
element_span_t::needs_end, // blockquote
element_span_t::end_optional, // body
element_span_t::immediate, // br
element_span_t::needs_end, // button
element_span_t::needs_end, // caption
element_span_t::needs_end, // center
element_span_t::needs_end, // cite
element_span_t::needs_end, // code
element_span_t::immediate, // col
element_span_t::end_optional, // colgroup
element_span_t::needs_end, // comment
element_span_t::end_optional, // dd
element_span_t::needs_end, // del
element_span_t::needs_end, // dfn
element_span_t::needs_end, // dir
element_span_t::needs_end, // div
element_span_t::needs_end, // dl
element_span_t::end_optional, // dt
element_span_t::needs_end, // em
element_span_t::immediate, // embed
element_span_t::needs_end, // fieldset
element_span_t::needs_end, // font
element_span_t::needs_end, // form
element_span_t::immediate, // frame
element_span_t::needs_end, // frameset
element_span_t::needs_end, // h1
element_span_t::needs_end, // h2
element_span_t::needs_end, // h3
element_span_t::needs_end, // h4
element_span_t::needs_end, // h5
element_span_t::needs_end, // h6
element_span_t::end_optional, // head
element_span_t::immediate, // hr
element_span_t::end_optional, // html
element_span_t::needs_end, // i
element_span_t::needs_end, // iframe
element_span_t::immediate, // img
element_span_t::immediate, // input
element_span_t::needs_end, // ins
element_span_t::immediate, // isindex
element_span_t::needs_end, // kbd
element_span_t::needs_end, // label
element_span_t::needs_end, // legend
element_span_t::end_optional, // li
element_span_t::immediate, // link
element_span_t::needs_end, // listing
element_span_t::needs_end, // map
element_span_t::needs_end, // marquee
element_span_t::needs_end, // menu
element_span_t::immediate, // meta
element_span_t::immediate, // nextid
element_span_t::needs_end, // nobr
element_span_t::needs_end, // noembed
element_span_t::needs_end, // noframes
element_span_t::needs_end, // noscript
element_span_t::needs_end, // object
element_span_t::needs_end, // ol
element_span_t::needs_end, // optgroup
element_span_t::end_optional, // option
element_span_t::end_optional, // p
element_span_t::immediate, // param
element_span_t::end_optional, // plaintext
element_span_t::needs_end, // pre
element_span_t::needs_end, // q
element_span_t::immediate, // rt
element_span_t::needs_end, // ruby
element_span_t::needs_end, // s
element_span_t::needs_end, // samp
element_span_t::needs_end, // script
element_span_t::needs_end, // select
element_span_t::needs_end, // small
element_span_t::needs_end, // span
element_span_t::needs_end, // strike
element_span_t::needs_end, // strong
element_span_t::needs_end, // style
element_span_t::needs_end, // sub
element_span_t::needs_end, // sup
element_span_t::needs_end, // table
element_span_t::end_optional, // tbody
element_span_t::end_optional, // td
element_span_t::needs_end, // textarea
element_span_t::end_optional, // tfoot
element_span_t::end_optional, // th
element_span_t::end_optional, // thead
element_span_t::needs_end, // title
element_span_t::end_optional, // tr
element_span_t::needs_end, // tt
element_span_t::needs_end, // u
element_span_t::needs_end, // ul
element_span_t::needs_end, // var
element_span_t::immediate, // wbr
element_span_t::needs_end, // xmp
};
return element_t::a <= code && code <= element_t::xmp ?
lookup[static_cast<size_t>(code) - static_cast<size_t>(element_t::a)] :
element_span_t::needs_end;
}
static inline bool is_fontstyle(_In_ element_t code)
{
switch (code) {
case element_t::tt:
case element_t::i:
case element_t::b:
case element_t::u:
case element_t::s:
case element_t::strike:
case element_t::blink:
case element_t::big:
case element_t::small:
return true;
};
return false;
}
static inline bool is_phrase(_In_ element_t code)
{
switch (code) {
case element_t::em:
case element_t::strong:
case element_t::dfn:
case element_t::code:
case element_t::samp:
case element_t::kbd:
case element_t::var:
case element_t::cite:
case element_t::abbr:
case element_t::acronym:
case element_t::xmp:
return true;
};
return false;
}
static inline bool is_special(_In_ element_t code)
{
switch (code) {
case element_t::a:
case element_t::img:
case element_t::applet:
case element_t::object:
case element_t::embed:
case element_t::font:
case element_t::basefont:
case element_t::br:
case element_t::wbr:
case element_t::rt:
case element_t::script:
case element_t::map:
case element_t::q:
case element_t::sub:
case element_t::sup:
case element_t::ruby:
case element_t::span:
case element_t::bdo:
case element_t::iframe:
case element_t::nobr:
return true;
};
return false;
}
static inline bool is_formctrl(_In_ element_t code)
{
switch (code) {
case element_t::input:
case element_t::select:
case element_t::textarea:
case element_t::label:
case element_t::button:
return true;
};
return false;
}
static inline bool is_inline(_In_ element_t code)
{
return
code == element_t::PCDATA ||
is_fontstyle(code) ||
is_phrase(code) ||
is_special(code) ||
is_formctrl(code);
}
static inline bool is_heading(_In_ element_t code)
{
switch (code) {
case element_t::h1:
case element_t::h2:
case element_t::h3:
case element_t::h4:
case element_t::h5:
case element_t::h6:
return true;
};
return false;
}
static inline bool is_list(_In_ element_t code)
{
switch (code) {
case element_t::ul:
case element_t::ol:
case element_t::dir:
case element_t::menu:
return true;
};
return false;
}
static inline bool is_preformatted(_In_ element_t code)
{
switch (code) {
case element_t::pre:
case element_t::listing:
return true;
}
return false;
}
static inline bool is_block(_In_ element_t code)
{
if (is_heading(code) ||
is_list(code) ||
is_preformatted(code)) return true;
switch (code) {
case element_t::p:
case element_t::dl:
case element_t::div:
case element_t::center:
case element_t::marquee:
case element_t::noscript:
case element_t::noframes:
case element_t::noembed:
case element_t::blockquote:
case element_t::form:
case element_t::isindex:
case element_t::hr:
case element_t::table:
case element_t::fieldset:
case element_t::address:
return true;
};
return false;
}
static inline bool is_flow(_In_ element_t code)
{
return is_block(code) || is_inline(code);
}
static inline bool is_head_content(_In_ element_t code)
{
switch (code) {
case element_t::title:
case element_t::isindex:
case element_t::base:
case element_t::nextid:
return true;
};
return false;
}
static inline bool is_head_misc(_In_ element_t code)
{
switch (code) {
case element_t::script:
case element_t::style:
case element_t::meta:
case element_t::link:
case element_t::object:
return true;
};
return false;
}
static inline bool is_pre_exclusion(_In_ element_t code)
{
switch (code) {
case element_t::img:
case element_t::object:
case element_t::applet:
case element_t::embed:
case element_t::big:
case element_t::small:
case element_t::sub:
case element_t::sup:
case element_t::ruby:
case element_t::font:
case element_t::basefont:
case element_t::nobr:
return true;
};
return false;
}
static inline bool is_html_content(_In_ element_t code)
{
switch (code) {
case element_t::head:
case element_t::body:
case element_t::frameset:
return true;
};
return false;
}
static inline bool is_group(_In_ element_t code)
{
if (is_block(code) ||
is_html_content(code) ||
is_head_content(code)) return true;
switch (code) {
case element_t::col:
case element_t::colgroup:
case element_t::dd:
case element_t::dir:
case element_t::dt:
case element_t::frame:
case element_t::iframe:
case element_t::legend:
case element_t::td:
case element_t::th:
case element_t::tr:
return true;
};
return false;
}
static inline bool may_contain(_In_ element_t parent, _In_ element_t child)
{
if (child == element_t::unknown || child == element_t::comment)
return true;
if (is_fontstyle(parent) || is_phrase(parent))
return is_inline(child);
if (is_heading(parent))
return is_inline(child);
switch (parent) {
case element_t::a: return is_inline(child) && child != element_t::a;
case element_t::address: return is_inline(child) || child == element_t::p;
case element_t::applet: return is_flow(child) || child == element_t::param;
case element_t::area: return false;
case element_t::base: return false;
case element_t::basefont: return false;
case element_t::bdo: return is_inline(child);
case element_t::blockquote: return is_flow(child);
case element_t::body: return is_flow(child) || child == element_t::ins || child == element_t::del;
case element_t::br: return false;
case element_t::button: return is_flow(child) && !is_formctrl(child) && child != element_t::a && child != element_t::form && child != element_t::isindex && child != element_t::fieldset && child != element_t::iframe;
case element_t::caption: return is_inline(child);
case element_t::center: return is_flow(child);
case element_t::col: return false;
case element_t::colgroup: return child == element_t::col;
case element_t::comment: return child == element_t::CDATA;
case element_t::dd: return is_flow(child);
case element_t::del: return is_flow(child);
case element_t::dir: return child == element_t::li;
case element_t::div: return is_flow(child);
case element_t::dl: return child == element_t::dt || child == element_t::dd;
case element_t::dt: return is_inline(child);
case element_t::embed: return is_flow(child) || child == element_t::param;
case element_t::fieldset: return is_flow(child) || child == element_t::legend || child == element_t::PCDATA;
case element_t::font: return is_inline(child);
case element_t::form: return is_flow(child) && child != element_t::form;
case element_t::frame: return false;
case element_t::frameset: return child == element_t::frameset || child == element_t::frame || child == element_t::noframes;
case element_t::head: return is_head_content(child) || is_head_misc(child);
case element_t::hr: return false;
case element_t::html: return is_html_content(child);
case element_t::iframe: return is_flow(child);
case element_t::img: return false;
case element_t::input: return false;
case element_t::ins: return is_flow(child);
case element_t::isindex: return false;
case element_t::label: return is_inline(child) && child != element_t::label;
case element_t::legend: return is_inline(child);
case element_t::li: return is_flow(child);
case element_t::link: return false;
case element_t::listing: return child == element_t::CDATA;
case element_t::map: return is_block(child) || child == element_t::area;
case element_t::marquee: return is_flow(child);
case element_t::menu: return child == element_t::li;
case element_t::meta: return false;
case element_t::nobr: return is_inline(child) || child == element_t::wbr;
case element_t::noframes: return (is_flow(child) || child == element_t::body) && child != element_t::noframes;
case element_t::noscript: return is_flow(child);
case element_t::noembed: return is_flow(child);
case element_t::object: return is_flow(child) || child == element_t::param;
case element_t::ol: return child == element_t::li;
case element_t::optgroup: return child == element_t::option;
case element_t::option: return child == element_t::PCDATA;
case element_t::p: return is_inline(child);
case element_t::param: return false;
case element_t::plaintext: return is_flow(child);
case element_t::pre: return is_inline(child) && !is_pre_exclusion(child);
case element_t::q: return is_inline(child);
case element_t::rt: return false;
case element_t::ruby: return is_inline(child);
case element_t::script: return child == element_t::CDATA;
case element_t::select: return child == element_t::optgroup || child == element_t::option;
case element_t::span: return is_inline(child);
case element_t::style: return child == element_t::CDATA;
case element_t::sub: return is_inline(child);
case element_t::sup: return is_inline(child);
case element_t::table: return child == element_t::caption || child == element_t::col || child == element_t::colgroup || child == element_t::thead || child == element_t::tfoot || child == element_t::tbody;
case element_t::tbody: return child == element_t::tr;
case element_t::td: return is_flow(child);
case element_t::textarea: return child == element_t::PCDATA;
case element_t::tfoot: return child == element_t::tr;
case element_t::th: return is_flow(child);
case element_t::thead: return child == element_t::tr;
case element_t::title: return child == element_t::PCDATA;
case element_t::tr: return child == element_t::td || child == element_t::th;
case element_t::ul: return child == element_t::li;
case element_t::wbr: return false;
case element_t::unknown: return true;
}
return false;
}
template <class T>
static inline bool is_uri(_In_ element_t code, _In_reads_or_z_opt_(num_chars) const T* attr_name, _In_ size_t num_chars)
{
_Assume_(attr_name || !num_chars);
switch (code) {
case element_t::a: return !stdex::strnicmp(attr_name, num_chars, "href", SIZE_MAX);
case element_t::applet: return !stdex::strnicmp(attr_name, num_chars, "code", SIZE_MAX) ||
!stdex::strnicmp(attr_name, num_chars, "codebase", SIZE_MAX) ||
!stdex::strnicmp(attr_name, num_chars, "src", SIZE_MAX);
case element_t::area: return !stdex::strnicmp(attr_name, num_chars, "href", SIZE_MAX);
case element_t::base: return !stdex::strnicmp(attr_name, num_chars, "href", SIZE_MAX);
case element_t::bgsound: return !stdex::strnicmp(attr_name, num_chars, "src", SIZE_MAX);
case element_t::blockquote: return !stdex::strnicmp(attr_name, num_chars, "cite", SIZE_MAX);
case element_t::body: return !stdex::strnicmp(attr_name, num_chars, "background", SIZE_MAX);
case element_t::comment: return !stdex::strnicmp(attr_name, num_chars, "data", SIZE_MAX);
case element_t::del: return !stdex::strnicmp(attr_name, num_chars, "cite", SIZE_MAX);
case element_t::embed: return !stdex::strnicmp(attr_name, num_chars, "pluginspage", SIZE_MAX) ||
!stdex::strnicmp(attr_name, num_chars, "src", SIZE_MAX);
case element_t::form: return !stdex::strnicmp(attr_name, num_chars, "action", SIZE_MAX);
case element_t::frame: return !stdex::strnicmp(attr_name, num_chars, "longdesc", SIZE_MAX) ||
!stdex::strnicmp(attr_name, num_chars, "src", SIZE_MAX);
case element_t::head: return !stdex::strnicmp(attr_name, num_chars, "profile", SIZE_MAX);
case element_t::iframe: return !stdex::strnicmp(attr_name, num_chars, "longdesc", SIZE_MAX) ||
!stdex::strnicmp(attr_name, num_chars, "src", SIZE_MAX);
case element_t::img: return !stdex::strnicmp(attr_name, num_chars, "longdesc", SIZE_MAX) ||
!stdex::strnicmp(attr_name, num_chars, "lowsrc", SIZE_MAX) ||
!stdex::strnicmp(attr_name, num_chars, "src", SIZE_MAX) ||
!stdex::strnicmp(attr_name, num_chars, "usemap", SIZE_MAX);
case element_t::input: return !stdex::strnicmp(attr_name, num_chars, "lowsrc", SIZE_MAX) ||
!stdex::strnicmp(attr_name, num_chars, "src", SIZE_MAX) ||
!stdex::strnicmp(attr_name, num_chars, "usemap", SIZE_MAX);
case element_t::ins: return !stdex::strnicmp(attr_name, num_chars, "cite", SIZE_MAX);
case element_t::link: return !stdex::strnicmp(attr_name, num_chars, "href", SIZE_MAX);
case element_t::object: return !stdex::strnicmp(attr_name, num_chars, "basehref", SIZE_MAX) ||
!stdex::strnicmp(attr_name, num_chars, "classid", SIZE_MAX) ||
!stdex::strnicmp(attr_name, num_chars, "code", SIZE_MAX) ||
!stdex::strnicmp(attr_name, num_chars, "codebase", SIZE_MAX) ||
!stdex::strnicmp(attr_name, num_chars, "data", SIZE_MAX) ||
!stdex::strnicmp(attr_name, num_chars, "usemap", SIZE_MAX);
case element_t::q: return !stdex::strnicmp(attr_name, num_chars, "cite", SIZE_MAX);
case element_t::script: return !stdex::strnicmp(attr_name, num_chars, "src", SIZE_MAX);
case element_t::table: return !stdex::strnicmp(attr_name, num_chars, "background", SIZE_MAX);
case element_t::td: return !stdex::strnicmp(attr_name, num_chars, "background", SIZE_MAX);
case element_t::th: return !stdex::strnicmp(attr_name, num_chars, "background", SIZE_MAX);
}
return false;
}
template <class T>
static inline bool is_localizable(element_t code, const T* attr_name, size_t num_chars)
{
_Assume_(attr_name || !num_chars);
if (!stdex::strnicmp(attr_name, num_chars, "title", SIZE_MAX))
return true;
switch (code) {
case element_t::applet: return !stdex::strnicmp(attr_name, num_chars, "alt", SIZE_MAX);
case element_t::area: return !stdex::strnicmp(attr_name, num_chars, "alt", SIZE_MAX);
case element_t::img: return !stdex::strnicmp(attr_name, num_chars, "alt", SIZE_MAX);
case element_t::input: return !stdex::strnicmp(attr_name, num_chars, "alt", SIZE_MAX);
case element_t::object: return !stdex::strnicmp(attr_name, num_chars, "alt", SIZE_MAX);
case element_t::table: return !stdex::strnicmp(attr_name, num_chars, "summary", SIZE_MAX);
case element_t::td: return !stdex::strnicmp(attr_name, num_chars, "abbr", SIZE_MAX);
case element_t::th: return !stdex::strnicmp(attr_name, num_chars, "abbr", SIZE_MAX);
}
return false;
}
};
class sequence;
using sequence_store = std::vector<std::unique_ptr<sequence>>;
class sequence
{
public:
stdex::parser::html_sequence_t type;
stdex::interval<size_t> interval;
sequence* parent;
sequence(_In_ stdex::parser::html_sequence_t _type = stdex::parser::html_sequence_t::unknown, _In_ size_t start = 0, size_t end = 0, _In_opt_ sequence* _parent = nullptr) :
type(_type),
interval(start, end),
parent(_parent)
{}
virtual ~sequence() {} // make polymorphic
};
class element : public sequence
{
public:
template <class T>
inline element(_Inout_ stdex::parser::basic_html_tag<T>&& tag, _In_z_ const T* src, _In_opt_ sequence* parent = nullptr) :
sequence(tag.type, tag.interval.start, tag.interval.end, parent),
code(element_code(src + tag.name.start, tag.name.size())),
name(std::move(tag.name)),
attributes(std::move(tag.attributes))
{}
template <class T>
static element_t element_code(_In_reads_z_(num_chars) const T* name, size_t num_chars)
{
static const struct {
const char* name;
element_t code;
} mapping[] = {
{ "a", element_t::a, },
{ "abbr", element_t::abbr, },
{ "acronym", element_t::acronym, },
{ "address", element_t::address, },
{ "applet", element_t::applet, },
{ "area", element_t::area, },
{ "b", element_t::b, },
{ "base", element_t::base, },
{ "basefont", element_t::basefont, },
{ "bdo", element_t::bdo, },
{ "bgsound", element_t::bgsound, },
{ "big", element_t::big, },
{ "blink", element_t::blink, },
{ "blockquote", element_t::blockquote, },
{ "body", element_t::body, },
{ "br", element_t::br, },
{ "button", element_t::button, },
{ "caption", element_t::caption, },
{ "center", element_t::center, },
{ "cite", element_t::cite, },
{ "code", element_t::code, },
{ "col", element_t::col, },
{ "colgroup", element_t::colgroup, },
{ "comment", element_t::comment, },
{ "dd", element_t::dd, },
{ "del", element_t::del, },
{ "dfn", element_t::dfn, },
{ "dir", element_t::dir, },
{ "div", element_t::div, },
{ "dl", element_t::dl, },
{ "dt", element_t::dt, },
{ "em", element_t::em, },
{ "embed", element_t::embed, },
{ "fieldset", element_t::fieldset, },
{ "font", element_t::font, },
{ "form", element_t::form, },
{ "frame", element_t::frame, },
{ "frameset", element_t::frameset, },
{ "h1", element_t::h1, },
{ "h2", element_t::h2, },
{ "h3", element_t::h3, },
{ "h4", element_t::h4, },
{ "h5", element_t::h5, },
{ "h6", element_t::h6, },
{ "head", element_t::head, },
{ "hr", element_t::hr, },
{ "html", element_t::html, },
{ "i", element_t::i, },
{ "iframe", element_t::iframe, },
{ "img", element_t::img, },
{ "input", element_t::input, },
{ "ins", element_t::ins, },
{ "isindex", element_t::isindex, },
{ "kbd", element_t::kbd, },
{ "label", element_t::label, },
{ "legend", element_t::legend, },
{ "li", element_t::li, },
{ "link", element_t::link, },
{ "listing", element_t::listing, },
{ "map", element_t::map, },
{ "marquee", element_t::marquee, },
{ "menu", element_t::menu, },
{ "meta", element_t::meta, },
{ "nextid", element_t::nextid, },
{ "nobr", element_t::nobr, },
{ "noembed", element_t::noembed, },
{ "noframes", element_t::noframes, },
{ "noscript", element_t::noscript, },
{ "object", element_t::object, },
{ "ol", element_t::ol, },
{ "optgroup", element_t::optgroup, },
{ "option", element_t::option, },
{ "p", element_t::p, },
{ "param", element_t::param, },
{ "plaintext", element_t::plaintext, },
{ "pre", element_t::pre, },
{ "q", element_t::q, },
{ "rt", element_t::rt, },
{ "ruby", element_t::ruby, },
{ "s", element_t::s, },
{ "samp", element_t::samp, },
{ "script", element_t::script, },
{ "select", element_t::select, },
{ "small", element_t::small, },
{ "span", element_t::span, },
{ "strike", element_t::strike, },
{ "strong", element_t::strong, },
{ "style", element_t::style, },
{ "sub", element_t::sub, },
{ "sup", element_t::sup, },
{ "table", element_t::table, },
{ "tbody", element_t::tbody, },
{ "td", element_t::td, },
{ "textarea", element_t::textarea, },
{ "tfoot", element_t::tfoot, },
{ "th", element_t::th, },
{ "thead", element_t::thead, },
{ "title", element_t::title, },
{ "tr", element_t::tr, },
{ "tt", element_t::tt, },
{ "u", element_t::u, },
{ "ul", element_t::ul, },
{ "var", element_t::var, },
{ "wbr", element_t::wbr, },
{ "xmp", element_t::xmp, },
};
#ifdef _DEBUG
// The mapping table MUST be sorted and all names in lowercase.
for (size_t i = 1; i < _countof(mapping); i++)
_Assume_(stdex::strcmp(mapping[i - 1].name, mapping[i].name) <= 0);
for (size_t i = 0; i < _countof(mapping); i++) {
for (size_t j = 0; mapping[i].name[j]; j++)
_Assume_(stdex::islower(mapping[i].name[j]) | stdex::isdigit(mapping[i].name[j]));
}
#endif
for (size_t i = 0, j = _countof(mapping); i < j; ) {
size_t m = (i + j) / 2;
int r = 0;
for (size_t i1 = 0, i2 = 0;;) {
if (!mapping[m].name[i1]) {
r = i2 >= num_chars || !name[i2] ? 0 : -1;
break;
}
if (i2 >= num_chars || !name[i2]) {
r = 1;
break;
}
auto chr = static_cast<char>(stdex::tolower(name[i2++]));
if (mapping[m].name[i1] > chr) {
r = 1;
break;
}
if (mapping[m].name[i1] < chr) {
r = -1;
break;
}
i1++;
}
if (r < 0)
i = m + 1;
else if (r > 0)
j = m;
else
return mapping[m].code;
}
return element_t::unknown;
}
public:
element_t code;
stdex::interval<size_t> name;
std::vector<stdex::parser::html_attribute> attributes;
};
class element_end;
class element_start : public element
{
public:
template <class T>
inline element_start(_Inout_ stdex::parser::basic_html_tag<T>&& tag, _In_z_ const T* src, _In_opt_ sequence* parent = nullptr, _In_opt_ sequence* _end = nullptr) :
element(std::move(tag), src, parent),
end(_end)
{}
public:
sequence* end;
};
class element_end : public sequence
{
public:
template <class T>
inline element_end(_Inout_ stdex::parser::basic_html_tag<T>&& tag, _In_z_ const T* src, _In_opt_ sequence* parent = nullptr, _In_opt_ element_start* _start = nullptr) :
sequence(tag.type, tag.interval.start, tag.interval.end, parent),
code(element::element_code(src + tag.name.start, tag.name.size())),
name(std::move(tag.name)),
start(_start)
{}
public:
element_t code;
stdex::interval<size_t> name;
element_start* start;
};
class declaration : public sequence
{
public:
template <class T>
inline declaration(_Inout_ stdex::parser::basic_html_tag<T>&& tag, _In_opt_ sequence* parent = nullptr) :
sequence(tag.type, tag.interval.start, tag.interval.end, parent),
name(std::move(tag.name)),
attributes(std::move(tag.attributes))
{}
public:
stdex::interval<size_t> name;
std::vector<stdex::parser::html_attribute> attributes;
};
class comment : public sequence
{
public:
template <class T>
inline comment(_Inout_ stdex::parser::basic_html_tag<T>&& tag, _In_opt_ sequence* parent = nullptr) :
sequence(tag.type, tag.interval.start, tag.interval.end, parent),
content(std::move(tag.name))
{}
public:
stdex::interval<size_t> content;
};
class instruction : public sequence
{
public:
template <class T>
inline instruction(_Inout_ stdex::parser::basic_html_tag<T>&& tag, _In_opt_ sequence* parent = nullptr) :
sequence(tag.type, tag.interval.start, tag.interval.end, parent),
content(std::move(tag.name))
{}
public:
stdex::interval<size_t> content;
};
template<class _Elem, class _Traits = std::char_traits<_Elem>, class _Alloc = std::allocator<_Elem>>
struct entity
{
stdex::interval<size_t> name;
std::basic_string<_Elem, _Traits, _Alloc> value;
};
template<class _Elem, class _Traits = std::char_traits<_Elem>, class _Alloc = std::allocator<_Elem>>
class parser;
template<class _Elem, class _Traits = std::char_traits<_Elem>, class _Alloc = std::allocator<_Elem>>
class document
{
public:
document() :
m_num_parsed(0),
m_charset(stdex::charset_id::system),
// Declaration parsing data
m_num_valid_conditions(0),
m_num_invalid_conditions(0),
m_is_cdata(false),
m_is_rcdata(false),
// Element parsing data
m_is_special_element(false)
{}
void clear()
{
m_source.clear();
m_num_parsed = 0;
m_charset = stdex::charset_id::system;
// Declaration parsing data
m_num_valid_conditions = m_num_invalid_conditions = 0;
m_is_cdata = m_is_rcdata = false;
m_entities.clear();
// Element parsing data
m_sequences.clear();
m_element_stack.clear();
m_is_special_element = false;
}
void append(_In_reads_or_z_opt_(num_chars) const _Elem* source, _In_ size_t num_chars = SIZE_MAX)
{
_Assume_(source || !num_chars);
m_source.append(source, stdex::strnlen(source, num_chars));
source = m_source.data();
num_chars = m_source.size();
for (size_t i = m_num_parsed; i < num_chars;) {
if (m_is_cdata || m_is_rcdata) {
if (m_condition_end.match(source, i, num_chars)) {
m_sequences.push_back(std::move(std::unique_ptr<sequence>(new sequence(
m_is_cdata ? stdex::parser::html_sequence_t::CDATA : stdex::parser::html_sequence_t::PCDATA,
m_num_parsed, i,
active_element()))));
m_is_cdata = m_is_rcdata = false;
i = m_num_parsed = m_condition_end.interval.end;
continue;
}
goto next_char;
}
if (m_num_invalid_conditions) {
if (m_condition_end.match(source, i, num_chars)) {
m_num_invalid_conditions--;
i = m_num_parsed = m_condition_end.interval.end;
continue;
}
goto next_char;
}
if (m_num_valid_conditions && m_condition_end.match(source, i, num_chars)) {
if (m_num_parsed < i)
m_sequences.push_back(std::move(std::unique_ptr<sequence>(new sequence(stdex::parser::html_sequence_t::text, m_num_parsed, i, active_element()))));
m_num_valid_conditions--;
i = m_num_parsed = m_condition_end.interval.end;
continue;
}
if (m_condition_start.match(source, i, num_chars)) {
auto condition_src(replace_entities(source + m_condition_start.condition.start, m_condition_start.condition.size()));
if (!stdex::strcmp(condition_src.c_str(), "CDATA"))
m_is_cdata = true;
else if (!stdex::strcmp(condition_src.c_str(), "RCDATA"))
m_is_rcdata = true;
if (m_num_invalid_conditions)
m_num_invalid_conditions++;
else if (!stdex::strcmp(condition_src.c_str(), "IGNORE"))
m_num_invalid_conditions++;
else
m_num_valid_conditions++;
i = m_num_parsed = m_condition_start.interval.end;
continue;
}
if (m_is_special_element) {
auto parent = active_element();
_Assume_(parent);
if (m_tag.match(source, i, num_chars) &&
m_tag.type == stdex::parser::html_sequence_t::element_end &&
element::element_code(source + m_tag.name.start, m_tag.name.size()) == parent->code)
{
if (m_num_parsed < i)
m_sequences.push_back(std::move(std::unique_ptr<sequence>(new sequence(stdex::parser::html_sequence_t::text, m_num_parsed, i, parent))));
i = m_num_parsed = m_tag.interval.end;
std::unique_ptr<element_end> e(new element_end(std::move(m_tag), source, parent->parent, parent));
parent->end = e.get();
m_sequences.push_back(std::move(e));
m_element_stack.pop_back();
m_is_special_element = false;
continue;
}
goto next_char;
}
if (m_tag.match(source, i, num_chars)) {
if (m_num_parsed < i)
m_sequences.push_back(std::move(std::unique_ptr<sequence>(new sequence(stdex::parser::html_sequence_t::text, m_num_parsed, i, active_element()))));
i = m_num_parsed = m_tag.interval.end;
switch (m_tag.type) {
case stdex::parser::html_sequence_t::element:
case stdex::parser::html_sequence_t::element_start: {
std::unique_ptr<element> e(
m_tag.type == stdex::parser::html_sequence_t::element ? new element(std::move(m_tag), source) :
m_tag.type == stdex::parser::html_sequence_t::element_start ? new element_start(std::move(m_tag), source) :
nullptr);
// Does this tag end any of the started elements?
for (size_t j = m_element_stack.size(); j--; ) {
auto starting_tag = m_element_stack[j];
_Assume_(starting_tag && starting_tag->type == stdex::parser::html_sequence_t::element_start);
if (element_traits::may_contain(starting_tag->code, e->code)) {
e->parent = starting_tag;
break;
}
e->parent = starting_tag->parent;
starting_tag->end = e.get();
m_element_stack.resize(j);
}
if (e->type == stdex::parser::html_sequence_t::element_start) {
auto e_start = static_cast< element_start*>(e.get());
if (element_traits::span(e->code) == element_span_t::immediate)
e_start->end = e.get();
else {
m_element_stack.push_back(e_start);
switch (e->code) {
case element_t::code:
case element_t::comment:
case element_t::script:
case element_t::style:
m_is_special_element = true;
break;
}
}
}
if (e->code == element_t::meta && m_charset == stdex::charset_id::system) {
bool is_content_type = false;
stdex::parser::html_attribute* content_attr = nullptr;
for (auto& attr : e->attributes) {
if (!stdex::strnicmp(source + attr.name.start, attr.name.size(), "http-equiv", SIZE_MAX) &&
!stdex::strnicmp(source + attr.value.start, attr.value.size(), "content-type", SIZE_MAX))
is_content_type = true;
else if (!stdex::strnicmp(source + attr.name.start, attr.name.size(), "content", SIZE_MAX))
content_attr = &attr;
}
if (is_content_type && content_attr) {
// <meta http-equiv="Content-Type" content="..."> found.
stdex::parser::basic_mime_type<_Elem> content;
if (content.match(source, content_attr->value.start, content_attr->value.end) &&
content.charset)
{
std::string str;
str.reserve(content.charset.size());
for (size_t j = content.charset.start; j < content.charset.end; ++j)
str.push_back(static_cast<char>(source[j]));
m_charset = stdex::charset_from_name(str.c_str());
}
}
}
m_sequences.push_back(std::move(e));
break;
}
case stdex::parser::html_sequence_t::element_end: {
std::unique_ptr<element_end> e(new element_end(std::move(m_tag), source, active_element()));
for (size_t j = m_element_stack.size(); j--; ) {
auto starting_tag = m_element_stack[j];
_Assume_(starting_tag && starting_tag->type == stdex::parser::html_sequence_t::element_start);
if (starting_tag->code == e->code ||
starting_tag->code == element_t::unknown && e->code == element_t::unknown && !stdex::strnicmp(source + starting_tag->name.start, starting_tag->name.size(), source + e->name.start, e->name.size()))
{
e->start = starting_tag;
e->parent = starting_tag->parent;
starting_tag->end = e.get();
m_element_stack.resize(j);
break;
}
}
m_sequences.push_back(std::move(e));
break;
}
case stdex::parser::html_sequence_t::declaration:
if (m_tag.attributes.size() > 3 &&
!stdex::strnicmp(source + m_tag.attributes[0].name.start, m_tag.attributes[0].name.size(), "entity", SIZE_MAX))
{
if (!stdex::strncmp(source + m_tag.attributes[1].name.start, m_tag.attributes[1].name.size(), "%", SIZE_MAX) &&
stdex::strncmp(source + m_tag.attributes[3].name.start, m_tag.attributes[3].name.size(), "SYSTEM", SIZE_MAX) &&
stdex::strncmp(source + m_tag.attributes[3].name.start, m_tag.attributes[3].name.size(), "PUBLIC", SIZE_MAX))
{
std::unique_ptr<entity<_Elem, _Traits, _Alloc>> e(new entity<_Elem, _Traits, _Alloc>());
e->name = m_tag.attributes[2].name;
e->value = std::move(replace_entities(source + m_tag.attributes[3].name.start, m_tag.attributes[3].name.size()));
m_entities.push_back(std::move(e));
}
// TODO: Parse & entities and entities in SYSTEM and PUBLIC external files.
}
m_sequences.push_back(std::move(std::unique_ptr<sequence>(new declaration(std::move(m_tag), active_element()))));
break;
case stdex::parser::html_sequence_t::comment:
m_sequences.push_back(std::move(std::unique_ptr<sequence>(new comment(std::move(m_tag), active_element()))));
break;
case stdex::parser::html_sequence_t::instruction:
m_sequences.push_back(std::move(std::unique_ptr<sequence>(new instruction(std::move(m_tag), active_element()))));
break;
default:
throw std::invalid_argument("unknown tag type");
}
continue;
}
next_char:
if (m_any_char.match(source, i, num_chars)) {
// Skip any character, but don't declare it as parsed yet. It might be a part of unfinished tag.
i = m_any_char.interval.end;
}
else
break;
}
}
void finalize()
{
size_t i = m_source.size();
if (m_num_parsed < i)
m_sequences.push_back(std::move(std::unique_ptr<sequence>(new sequence(stdex::parser::html_sequence_t::text, m_num_parsed, i, active_element()))));
m_num_parsed = i;
m_element_stack.clear();
}
inline void assign(_In_reads_or_z_opt_(num_chars) const _Elem* source, _In_ size_t num_chars = SIZE_MAX)
{
clear();
append(source, num_chars);
finalize();
}
inline const std::basic_string<_Elem, _Traits, _Alloc>& source() const { return m_source; }
friend class parser<_Elem, _Traits, _Alloc>;
protected:
inline element_start* active_element() const
{
return m_element_stack.empty() ? nullptr : m_element_stack.back();
}
std::basic_string<_Elem, _Traits, _Alloc> replace_entities(_In_reads_or_z_opt_(num_chars) const _Elem* input, _In_ size_t num_chars) const
{
_Assume_(input || !num_chars);
const size_t num_entities = m_entities.size();
const _Elem* source = m_source.data();
std::basic_string<_Elem, _Traits, _Alloc> output;
for (size_t i = 0; i < num_chars && input[i];) {
if (input[i] == '%') {
for (size_t j = 0; j < num_entities; j++) {
auto& e = m_entities[j];
size_t entity_size = e->name.size();
if (i + entity_size + 1 < num_chars &&
!stdex::strncmp(input + i + 1, source + e->name.start, entity_size) &&
input[i + entity_size + 1] == ';')
{
output += e->value;
i += entity_size + 2;
goto next_char;
}
}
throw std::runtime_error("undefined entity");
}
output += input[i++];
next_char:;
}
return output;
}
protected:
std::basic_string<_Elem, _Traits, _Alloc> m_source;
size_t m_num_parsed;
stdex::charset_id m_charset;
// Declaration parsing data
size_t m_num_valid_conditions;
size_t m_num_invalid_conditions;
bool m_is_cdata;
bool m_is_rcdata;
stdex::parser::basic_html_declaration_condition_start<_Elem> m_condition_start;
stdex::parser::basic_html_declaration_condition_end<_Elem> m_condition_end;
stdex::parser::basic_any_cu<_Elem> m_any_char;
std::vector<std::unique_ptr<entity<_Elem, _Traits, _Alloc>>> m_entities;
// Element parsing data
stdex::parser::basic_html_tag<_Elem> m_tag;
sequence_store m_sequences;
std::vector<element_start*> m_element_stack;
bool m_is_special_element;
};
enum class token_t {
root = 0,
complete,
starting,
ending,
url,
};
constexpr size_t token_tag_max =
sizeof(void*) * 2 // Memory address in hexadecimal
+ 2 // Leading and trailing parenthesis
+ 1; // Zero terminator
constexpr char token_tag_start = '\x12';
constexpr char token_tag_end = '\x13';
class token
{
protected:
inline token(_In_ token_t _type = token_t::root, _In_opt_ sequence* _sequence = nullptr, _In_ uintptr_t _data = 0) :
type(_type),
sequence(_sequence),
data(_data)
{}
template<class _Elem, class _Traits, class _Alloc>
friend class parser;
public:
virtual ~token() {} // make polymorphic
template<class _Traits = std::char_traits<char>, class _Alloc = std::allocator<char>>
inline size_t append_tag(_Inout_ std::basic_string<char, _Traits, _Alloc>& str) const
{
size_t n = str.size();
// Use %X instead of %p to ommit leading zeros and save space.
stdex::appendf(str, "%c%zX%c", stdex::locale_C.get(), token_tag_start, reinterpret_cast<uintptr_t>(this), token_tag_end);
return str.size() - n;
}
template<class _Traits = std::char_traits<wchar_t>, class _Alloc = std::allocator<wchar_t>>
inline size_t append_tag(_Inout_ std::basic_string<wchar_t, _Traits, _Alloc>& str) const
{
// Use %X instead of %p to ommit leading zeros and save space.
return stdex::appendf(str, L"%c%zX%c", stdex::locale_C.get(), static_cast<wchar_t>(token_tag_start), reinterpret_cast<uintptr_t>(this), static_cast<wchar_t>(token_tag_end));
}
template<class T>
static inline token* parse_tag(const T* str, size_t& offset)
{
if (str[offset] != static_cast<T>(token_tag_start))
return nullptr;
// Locate tag end.
size_t end;
for (end = offset + 1; ; end++) {
if (!str[end])
return nullptr;
if (str[end] == token_tag_end)
break;
}
// Parse hexadecimal token memory address.
token* t = reinterpret_cast<token*>(stdex::strtouint<T, uintptr_t>(str + offset + 1, end - offset - 1, nullptr, 16));
if (!t)
throw std::invalid_argument("null token");
offset = end + 1;
return t;
}
public:
token_t type;
sequence* sequence;
uintptr_t data;
};
using token_vector = std::vector<std::unique_ptr<token>>;
using token_list = std::list<token*>;
enum text_type_flag_t : uint32_t {
has_tokens = 1 << 0,
has_text = 1 << 1,
is_title = 1 << 2,
is_bullet = 1 << 3,
};
template<class _Elem, class _Traits = std::char_traits<_Elem>, class _Alloc = std::allocator<_Elem>>
class text_token : public token
{
protected:
inline text_token(
_In_ token_t type = token_t::complete,
_In_reads_or_z_opt_(num_chars) const _Elem* _text = nullptr, _In_ size_t num_chars = 0,
_In_ uint32_t _text_type = 0,
_In_opt_ stdex::html::sequence* sequence = nullptr, _In_ uintptr_t data = 0) :
token(type, sequence, data),
text(_text, num_chars),
text_type(_text_type)
{}
friend class parser<_Elem, _Traits, _Alloc>;
public:
std::basic_string<_Elem, _Traits, _Alloc> text;
uint32_t text_type;
stdex::mapping_vector<size_t> mapping;
};
template<class _Elem, class _Traits = std::char_traits<_Elem>, class _Alloc = std::allocator<_Elem>>
class starting_token : public text_token<_Elem, _Traits, _Alloc>
{
protected:
inline starting_token(
_In_reads_or_z_opt_(num_chars_text) const _Elem* _text = nullptr, _In_ size_t num_chars_text = 0,
_In_reads_or_z_opt_(num_chars_name) const _Elem* _name = nullptr, _In_ size_t num_chars_name = 0,
_In_ uint32_t text_type = 0,
_In_opt_ stdex::html::sequence* sequence = nullptr,
_In_opt_ stdex::html::sequence* _end_sequence = nullptr,
_In_ uintptr_t data = 0) :
text_token(token_t::starting, _text, num_chars_text, text_type, sequence, data),
name(_name, num_chars_name),
end_sequence(_end_sequence)
{}
friend class parser<_Elem, _Traits, _Alloc>;
public:
std::basic_string<_Elem, _Traits, _Alloc> name;
stdex::html::sequence* end_sequence;
};
enum class token_url_t {
plain = 0, // URL is not using any particular encoding scheme (as-is)
sgml, // URL is encoded using SGML entities
css, // URL is encoded using CSS escaping scheme
};
template<class _Elem, class _Traits = std::char_traits<_Elem>, class _Alloc = std::allocator<_Elem>>
class url_token : public token
{
protected:
inline url_token(
_In_reads_or_z_opt_(num_chars) const _Elem* _url = nullptr, _In_ size_t num_chars = 0,
token_url_t _encoding = token_url_t::plain,
_In_opt_ stdex::html::sequence* sequence = nullptr, _In_ uintptr_t data = 0) :
token(token_t::url, sequence, data),
url(_url, num_chars),
encoding(_encoding)
{}
friend class parser<_Elem, _Traits, _Alloc>;
public:
std::basic_string<_Elem, _Traits, _Alloc> url;
token_url_t encoding;
};
struct inserted_token {
token* token;
std::list<stdex::html::token*> active_tokens;
size_t word_index;
bool after_word;
};
using inserted_token_list = std::list<inserted_token>;
template<class _Elem, class _Traits, class _Alloc>
class parser
{
public:
inline parser(
_In_ const document<_Elem, _Traits, _Alloc>& document,
_In_reads_or_z_opt_(num_chars) const stdex::schar_t* url = nullptr, _In_ size_t num_chars = 0,
_In_ bool parse_frames = false, _In_ stdex::progress<size_t>* progress = nullptr) :
m_document(document),
m_url(url, stdex::strnlen(url, num_chars)),
m_parse_frames(parse_frames),
m_progress(progress),
m_source(nullptr)
{}
text_token<_Elem, _Traits, _Alloc>* parse()
{
_Assume_(m_tokens.empty());
if (m_progress) {
m_progress->set_range(0, m_document.source().size());
m_progress->set(0);
}
m_source = m_document.source().data();
m_offset = m_document.m_sequences.begin();
return parse(m_document.m_sequences.end());
}
static void link(_Inout_ std::basic_string<_Elem, _Traits, _Alloc>& source, _In_ const text_token<_Elem, _Traits, _Alloc>* t)
{
_Assume_(t);
_Assume_(
t->type == token_t::complete ||
t->type == token_t::starting ||
t->type == token_t::ending ||
t->type == token_t::root);
if (t->text_type & has_tokens) {
const _Elem* root = t->text.data();
for (size_t i = 0, num_chars = t->text.size(); i < num_chars && root[i];) {
_Assume_(root[i] != token_tag_end);
const token* t2 = token::parse_tag(root, i);
if (t2) {
switch (t2->type) {
case token_t::complete:
case token_t::starting:
case token_t::ending:
case token_t::root:
link(source, dynamic_cast<const text_token<_Elem, _Traits, _Alloc>*>(t2));
break;
case token_t::url: {
auto t2_url = dynamic_cast<const url_token<_Elem, _Traits, _Alloc>*>(t2);
switch (t2_url->encoding) {
case token_url_t::plain:
source += t2_url->url;
break;
case token_url_t::sgml:
escape(source, t2_url->url.data(), t2_url->url.size());
break;
case token_url_t::css:
css_escape(source, t2_url->url.data(), t2_url->url.size());
break;
default:
throw std::invalid_argument("unsupported URL encoding");
}
break;
}
default:
throw std::invalid_argument("unsupported token type");
}
}
else if (t->text_type & has_text) {
escape_min(source, root[i]);
i++;
}
else
source += root[i++];
}
}
else if (t->text_type & has_text) {
// Token contains no references to other tokens. But, it does contain text that requires escaping.
escape_min(source, t->text.data(), t->text.size());
}
else
source += t->text;
}
static void start_tokens(_Inout_ std::basic_string<_Elem, _Traits, _Alloc>& source, _Inout_ token_list& active_tokens, _In_ const token_list& new_tokens, _In_ token_list::const_iterator from)
{
for (; from != new_tokens.cend(); ++from) {
auto t = *from;
t->append_tag(source);
active_tokens.push_back(t);
}
}
token_list::const_iterator end_tokens(_Inout_ std::basic_string<_Elem, _Traits, _Alloc>& source, _Inout_ token_list& active_tokens, _In_ const token_list& new_tokens)
{
// Skip matching tokens in active_tokens and new_tokens.
token_list::const_iterator i1, i2;
for (i1 = active_tokens.cbegin(), i2 = new_tokens.cbegin(); i1 != active_tokens.cend(); ++i1, ++i2) {
if (i2 == new_tokens.cend() || *i1 != *i2) {
// Got two tokens, where lists don't match anymore, or new_tokens list is out.
// End tokens not relevant anymore in reverse order of starting.
for (auto i = active_tokens.cend(); i != active_tokens.cbegin(); ) {
auto t1 = dynamic_cast< starting_token<_Elem, _Traits, _Alloc>*>(*(--i));
_Assume_(t1 && t1->type == token_t::starting);
std::unique_ptr<text_token<_Elem, _Traits, _Alloc>> t2(new text_token<_Elem, _Traits, _Alloc>(token_t::ending));
t2->text.reserve(t1->name.size() + 3);
t2->text += '<';
t2->text += '/';
t2->text += t1->name;
t2->text += '>';
append_token(std::move(t2), source);
// Pop the active token.
if (i1 == i) {
active_tokens.erase(i);
break;
}
active_tokens.erase(i);
i = active_tokens.cend();
}
break;
}
}
return i2;
}
void append_inserted_tokens(_Inout_ std::basic_string<_Elem, _Traits, _Alloc>& source, _Inout_ inserted_token_list& inserted_tokens,
_In_ size_t word_index, _In_ bool after_word,
_Inout_ token_list& active_tokens)
{
for (auto i = inserted_tokens.begin(); i != inserted_tokens.end(); ) {
auto& t = *i;
_Assume_(t.token);
if (t.word_index == word_index && t.after_word == after_word) {
if (t.token->type != token_t::ending)
start_tokens(source, active_tokens, t.active_tokens, end_tokens(source, active_tokens, t.active_tokens));
t.token->append_tag(source);
inserted_tokens.erase(i++);
}
else
++i;
}
}
static void merge(_Inout_ token_list& a, _In_ const token_list& b)
{
for (auto i2 = b.begin(); i2 != b.end(); ++i2) {
auto t2 = *i2;
for (auto i1 = a.begin(); i1 != a.end(); ++i1) {
if (i1 == a.end()) {
a.push_back(t2);
break;
}
auto t1 = *i1;
if (t1 == t2)
break;
}
}
}
void make_absolute_url(std::basic_string<_Elem, _Traits, _Alloc>& rel)
{
_Unreferenced_(rel);
if (m_url.empty())
return;
// TODO: Implement!
}
inline const token_vector& tokens() const { return m_tokens; }
protected:
template <class T>
inline T* append_token(_Inout_ std::unique_ptr<T>&& token)
{
if (!token)
return nullptr;
auto t = token.get();
m_tokens.push_back(std::move(token));
return t;
}
template <class T>
inline size_t append_token(_Inout_ std::unique_ptr<T>&& token, _Inout_ std::basic_string<_Elem, _Traits, _Alloc>& source)
{
if (!token)
return 0;
size_t n = token->append_tag(source);
m_tokens.push_back(std::move(token));
return n;
}
text_token<_Elem, _Traits, _Alloc>* parse(_In_ const sequence_store::const_iterator& end, _In_ uint32_t text_type = 0)
{
stdex::mapping<size_t> rel;
std::unique_ptr<text_token<_Elem, _Traits, _Alloc>> token(new text_token<_Elem, _Traits, _Alloc>(
token_t::complete,
nullptr, 0,
text_type,
m_offset->get()));
while (m_offset != end) {
auto& s = *m_offset;
if (m_progress) {
if (m_progress->cancel())
throw stdex::user_cancelled();
m_progress->set(s->interval.start);
}
// No token_tag_start and token_tag_end chars, please.
_Assume_(
stdex::strnchr(m_source + s->interval.start, s->interval.size(), static_cast<_Elem>(token_tag_start)) == stdex::npos &&
stdex::strnchr(m_source + s->interval.start, s->interval.size(), static_cast<_Elem>(token_tag_end)) == stdex::npos);
if (s->type == stdex::parser::html_sequence_t::text) {
rel.from = s->interval.start;
token->mapping.push_back(rel);
stdex::sgml2strcat(token->text, m_source + s->interval.start, s->interval.size(), 0, rel, &token->mapping);
rel.to = token->text.size();
if (!(token->text_type & has_text) &&
!stdex::isblank(m_source + s->interval.start, s->interval.size()))
token->text_type |= has_text;
++m_offset;
}
else if (s->type == stdex::parser::html_sequence_t::element || s->type == stdex::parser::html_sequence_t::element_start) {
const element* s_el = static_cast<const element*>(s.get());
_Assume_(s_el);
const element_start* s_el_start = s->type == stdex::parser::html_sequence_t::element_start ? static_cast<const element_start*>(s.get()) : nullptr;
if (s_el->code == element_t::frameset && !m_parse_frames)
throw std::invalid_argument("<frameset> detected");
{
size_t offset = s->interval.start;
std::unique_ptr<text_token<_Elem, _Traits, _Alloc>> t(s->type == stdex::parser::html_sequence_t::element || element_traits::span(s_el_start->code) == element_span_t::immediate ?
new text_token<_Elem, _Traits, _Alloc>(token_t::complete, nullptr, 0, 0, s.get()) :
new starting_token<_Elem, _Traits, _Alloc>(nullptr, 0, m_source + s_el_start->name.start, s_el_start->name.size(), 0, s.get(), s_el_start->end));
// Copy the tag contents, but mind any attributes containing localizable text.
for (auto& a : s_el->attributes) {
if (a.value.empty() ||
stdex::isblank(m_source + a.value.start, a.value.size()))
continue;
if (element_traits::is_uri(s_el->code, m_source + a.name.start, a.name.size())) {
t->text.append(m_source + offset, a.value.start - offset);
std::unique_ptr<url_token<_Elem, _Traits, _Alloc>> t_url(new url_token<_Elem, _Traits, _Alloc>(
nullptr, 0,
token_url_t::sgml,
s.get()));
stdex::sgml2strcat(t_url->url, m_source + a.value.start, a.value.size());
append_token(std::move(t_url), t->text);
t->text_type |= has_tokens;
offset = a.value.end;
}
else if (element_traits::is_localizable(s_el->code, m_source + a.name.start, a.name.size())) {
t->text.append(m_source + offset, a.value.start - offset);
std::unique_ptr<text_token<_Elem, _Traits, _Alloc>> t_value(new text_token<_Elem, _Traits, _Alloc>(
token_t::complete,
nullptr, 0,
has_text | is_title,
s.get()));
stdex::mapping<size_t> rel_value(a.value.start, 0);
t_value->mapping.push_back(rel_value);
stdex::sgml2strcat(t_value->text, m_source + a.value.start, a.value.size(), 0, rel_value, &t_value->mapping);
append_token(std::move(t_value), t->text);
t->text_type |= has_tokens;
offset = a.value.end;
}
}
t->text.append(m_source + offset, s->interval.end - offset);
rel.from = s->interval.start;
token->mapping.push_back(rel);
rel.to += append_token(std::move(t), token->text);
token->text_type |= has_tokens;
}
++m_offset;
if (s_el_start) {
if (s_el_start->code == element_t::address ||
s_el_start->code == element_t::code ||
s_el_start->code == element_t::comment ||
s_el_start->code == element_t::cite ||
s_el_start->code == element_t::kbd ||
s_el_start->code == element_t::samp ||
s_el_start->code == element_t::script ||
s_el_start->code == element_t::style)
{
// Non-localizable
auto s_end = s_el_start->end;
_Assume_(s_end);
if (s->interval.end < s_end->interval.start) {
if (s_el_start->code != element_t::style) {
rel.from = s->interval.start;
token->mapping.push_back(rel);
rel.to += append_token(std::move(std::unique_ptr<text_token<_Elem, _Traits, _Alloc>>(
new text_token<_Elem, _Traits, _Alloc>(
token_t::complete,
m_source + s->interval.end, s_end->interval.start - s->interval.end,
0,
m_offset->get()))),
token->text);
}
else {
// Partially parse CSS. It may contain URLs we need to make absolute.
auto t = parse_css(s->interval.end, s_end->interval.start);
_Assume_(t);
rel.from = s->interval.start;
token->mapping.push_back(rel);
rel.to += t->append_tag(token->text);
}
token->text_type |= has_tokens;
}
while (m_offset != end && m_offset->get() != s_end)
++m_offset;
}
else if (element_traits::is_group(s_el_start->code)) {
auto limit = m_offset;
while (limit != end && limit->get() != s_el_start->end)
++limit;
auto t = parse(limit,
(element_traits::is_heading(s_el_start->code) || s_el_start->code == element_t::dt || s_el_start->code == element_t::title ? is_title : 0) |
(element_traits::is_list(s_el_start->code) ? is_bullet : 0));
rel.from = s->interval.start;
token->mapping.push_back(rel);
rel.to += t->append_tag(token->text);
token->text_type |= has_tokens;
}
}
}
else if (s->type == stdex::parser::html_sequence_t::element_end) {
rel.from = s->interval.start;
token->mapping.push_back(rel);
rel.to += append_token(std::move(std::unique_ptr<text_token<_Elem, _Traits, _Alloc>>(
new text_token<_Elem, _Traits, _Alloc>(
token_t::ending,
m_source + s->interval.start, s->interval.size(),
0,
s.get()))),
token->text);
token->text_type |= has_tokens;
++m_offset;
}
else {
// Declaration, instruction, (P)CDATA section, comment...
rel.from = s->interval.start;
token->mapping.push_back(rel);
rel.to += append_token(std::move(std::unique_ptr<text_token<_Elem, _Traits, _Alloc>>(
new text_token<_Elem, _Traits, _Alloc>(
token_t::complete,
m_source + s->interval.start, s->interval.size(),
0,
s.get()))),
token->text);
token->text_type |= has_tokens;
++m_offset;
}
}
return append_token(std::move(token));
}
text_token<_Elem, _Traits, _Alloc>* parse_css(size_t start, size_t end)
{
stdex::interval<size_t> section, content;
std::unique_ptr<text_token<_Elem, _Traits, _Alloc>> token(
new text_token<_Elem, _Traits, _Alloc>(
token_t::complete,
nullptr, 0,
0,
m_offset->get()));
for (;;) {
if (m_css_comment.match(m_source, start, end)) {
token->text.append(m_source + start, m_css_comment.interval.end - start);
start = m_css_comment.interval.end;
}
else if (m_css_cdo.match(m_source, start, end)) {
token->text.append(m_source + start, m_css_cdo.interval.end - start);
start = m_css_cdo.interval.end;
}
else if (m_css_cdc.match(m_source, start, end)) {
token->text.append(m_source + start, m_css_cdc.interval.end - start);
start = m_css_cdc.interval.end;
}
else if (
m_css_import.match(m_source, start, end) && (section = m_css_import.interval, content = m_css_import.content, true) ||
m_css_uri.match(m_source, start, end) && (section = m_css_uri.interval, content = m_css_uri.content, true))
{
std::unique_ptr<url_token<_Elem, _Traits, _Alloc>> t_url(
new url_token<_Elem, _Traits, _Alloc>(
nullptr, 0,
token_url_t::css,
m_offset->get()));
css_unescape(t_url->url, m_source + content.start, content.size());
token->text.append(m_source + start, content.start - start);
append_token(std::move(t_url), token->text);
token->text.append(m_source + content.end, section.end - content.end);
token->text_type |= has_tokens;
start = section.end;
}
else if (m_any_char.match(m_source, start, end)) {
token->text.append(m_source + start, m_any_char.interval.end - start);
start = m_any_char.interval.end;
}
else
break;
}
return append_token(std::move(token));
}
protected:
const document<_Elem, _Traits, _Alloc>& m_document;
const stdex::sys_string m_url;
const bool m_parse_frames;
stdex::progress<size_t>* m_progress;
const _Elem* m_source;
token_vector m_tokens;
sequence_store::const_iterator m_offset;
// For detecting URLs in CSS
stdex::parser::basic_css_cdo<_Elem> m_css_cdo;
stdex::parser::basic_css_cdc<_Elem> m_css_cdc;
stdex::parser::basic_css_comment<_Elem> m_css_comment;
stdex::parser::basic_css_string<_Elem> m_css_string;
stdex::parser::basic_css_uri<_Elem> m_css_uri;
stdex::parser::basic_css_import<_Elem> m_css_import;
stdex::parser::basic_any_cu<_Elem> m_any_char;
};
}
}
stdex::html::comment
HTML comment.
Definition html.hpp:1383
stdex::html::comment::content
stdex::interval< size_t > content
Comment content position in source.
Definition html.hpp:1392
stdex::html::declaration
HTML declaration.
Definition html.hpp:1365
stdex::html::declaration::name
stdex::interval< size_t > name
Declaration name position in source.
Definition html.hpp:1375
stdex::html::declaration::attributes
std::vector< stdex::parser::html_attribute > attributes
Declaration attribute positions in source.
Definition html.hpp:1376
stdex::html::document
HTML document.
Definition html.hpp:1432
stdex::html::document::m_element_stack
std::vector< element_start * > m_element_stack
LIFO stack of started elements.
Definition html.hpp:1767
stdex::html::document::finalize
void finalize()
Finalizes document when no more appending is planned.
Definition html.hpp:1683
stdex::html::document::m_charset
stdex::charset_id m_charset
Document charset.
Definition html.hpp:1752
stdex::html::document::m_is_rcdata
bool m_is_rcdata
Inside of RCDATA?
Definition html.hpp:1758
stdex::html::document::m_is_special_element
bool m_is_special_element
Inside of a special element (<SCRIPT>, <STYLE>, ...)?
Definition html.hpp:1768
stdex::html::document::append
void append(_In_reads_or_z_opt_(num_chars) const _Elem *source, size_t num_chars=SIZE_MAX)
Parses HTML source code by chunks.
Definition html.hpp:1472
stdex::html::document::m_sequences
sequence_store m_sequences
Store of sequences.
Definition html.hpp:1766
stdex::html::document::m_num_invalid_conditions
size_t m_num_invalid_conditions
Number of started invalid conditions.
Definition html.hpp:1756
stdex::html::document::assign
void assign(_In_reads_or_z_opt_(num_chars) const _Elem *source, size_t num_chars=SIZE_MAX)
Parses HTML document source code.
Definition html.hpp:1695
stdex::html::document::m_entities
std::vector< std::unique_ptr< entity< _Elem, _Traits, _Alloc > > > m_entities
Array of entities.
Definition html.hpp:1762
stdex::html::document::m_source
std::basic_string< _Elem, _Traits, _Alloc > m_source
Document HTML source code.
Definition html.hpp:1750
stdex::html::document::active_element
element_start * active_element() const
Returns starting tag of currently active element or nullptr if no element is known to be started.
Definition html.hpp:1713
stdex::html::document::replace_entities
std::basic_string< _Elem, _Traits, _Alloc > replace_entities(_In_reads_or_z_opt_(num_chars) const _Elem *input, size_t num_chars) const
Replaces entities with their content.
Definition html.hpp:1721
stdex::html::document::clear
void clear()
Empties document.
Definition html.hpp:1451
stdex::html::document::m_num_valid_conditions
size_t m_num_valid_conditions
Number of started valid conditions.
Definition html.hpp:1755
stdex::html::document::source
const std::basic_string< _Elem, _Traits, _Alloc > & source() const
Returns document HTML source code.
Definition html.hpp:1705
stdex::html::document::m_num_parsed
size_t m_num_parsed
Number of characters already parsed.
Definition html.hpp:1751
stdex::html::document::m_is_cdata
bool m_is_cdata
Inside of CDATA?
Definition html.hpp:1757
stdex::html::element_end
Ending tag of an HTML element </...>
Definition html.hpp:1345
stdex::html::element_end::name
stdex::interval< size_t > name
Element name position in source.
Definition html.hpp:1357
stdex::html::element_end::start
element_start * start
Corresponding starting tag.
Definition html.hpp:1358
stdex::html::element_end::code
element_t code
Element code.
Definition html.hpp:1356
stdex::html::element_start
Starting tag of an HTML element <...>
Definition html.hpp:1329
stdex::html::element_start::end
sequence * end
Corresponding ending tag of type element_end; When element is ended by a start of another element,...
Definition html.hpp:1338
stdex::html::element
HTML element <.../>
Definition html.hpp:1150
stdex::html::element::name
stdex::interval< size_t > name
Element name position in source.
Definition html.hpp:1319
stdex::html::element::attributes
std::vector< stdex::parser::html_attribute > attributes
Element attribute positions in source.
Definition html.hpp:1320
stdex::html::element::code
element_t code
Element code.
Definition html.hpp:1318
stdex::html::instruction
HTML instruction.
Definition html.hpp:1399
stdex::html::instruction::content
stdex::interval< size_t > content
Instruction content position in source.
Definition html.hpp:1408
stdex::html::parser
HTML parser.
Definition html.hpp:1989
stdex::html::parser::m_progress
stdex::progress< size_t > * m_progress
Progress indicator.
Definition html.hpp:2484
stdex::html::parser::parse_css
text_token< _Elem, _Traits, _Alloc > * parse_css(size_t start, size_t end)
Parses CSS.
Definition html.hpp:2430
stdex::html::parser::merge
static void merge(token_list &a, const token_list &b)
Adds tokens from list b to list a creating an union.
Definition html.hpp:2177
stdex::html::parser::end_tokens
token_list::const_iterator end_tokens(std::basic_string< _Elem, _Traits, _Alloc > &source, token_list &active_tokens, const token_list &new_tokens)
Pops ending tokens from the active token list and append their tags to the source code string.
Definition html.hpp:2110
stdex::html::parser::link
static void link(std::basic_string< _Elem, _Traits, _Alloc > &source, const text_token< _Elem, _Traits, _Alloc > *t)
Rebuilds HTML source code from the token tree.
Definition html.hpp:2025
stdex::html::parser::parse
text_token< _Elem, _Traits, _Alloc > * parse(const sequence_store::const_iterator &end, uint32_t text_type=0)
Recursively parses HTML document.
Definition html.hpp:2255
stdex::html::parser::m_source
const _Elem * m_source
HTML source code.
Definition html.hpp:2485
stdex::html::parser::m_tokens
token_vector m_tokens
HTML token storage.
Definition html.hpp:2486
stdex::html::parser::parse
text_token< _Elem, _Traits, _Alloc > * parse()
Parses HTML document.
Definition html.hpp:2005
stdex::html::parser::m_document
const document< _Elem, _Traits, _Alloc > & m_document
Document being analyzed.
Definition html.hpp:2481
stdex::html::parser::make_absolute_url
void make_absolute_url(std::basic_string< _Elem, _Traits, _Alloc > &rel)
Converts URL to absolute.
Definition html.hpp:2196
stdex::html::parser::append_token
size_t append_token(std::unique_ptr< T > &&token, std::basic_string< _Elem, _Traits, _Alloc > &source)
Adds token to the collection and appends its tag to the source code string.
Definition html.hpp:2238
stdex::html::parser::tokens
const token_vector & tokens() const
Returns collection of tokens.
Definition html.hpp:2209
stdex::html::parser::m_url
const stdex::sys_string m_url
Absolute document URL.
Definition html.hpp:2482
stdex::html::parser::m_parse_frames
const bool m_parse_frames
Parse frames.
Definition html.hpp:2483
stdex::html::parser::start_tokens
static void start_tokens(std::basic_string< _Elem, _Traits, _Alloc > &source, token_list &active_tokens, const token_list &new_tokens, token_list::const_iterator from)
Pushes tokens to the active token list and appends their tags to the source code string.
Definition html.hpp:2092
stdex::html::parser::append_token
T * append_token(std::unique_ptr< T > &&token)
Adds token to the collection.
Definition html.hpp:2220
stdex::html::parser::append_inserted_tokens
void append_inserted_tokens(std::basic_string< _Elem, _Traits, _Alloc > &source, inserted_token_list &inserted_tokens, size_t word_index, bool after_word, token_list &active_tokens)
Adds matching inserted tokens before/after the given word in source code.
Definition html.hpp:2153
stdex::html::parser::m_offset
sequence_store::const_iterator m_offset
Index of active section.
Definition html.hpp:2487
stdex::html::sequence
Base class for HTML sequences.
Definition html.hpp:1131
stdex::html::sequence::interval
stdex::interval< size_t > interval
Sequence position in source.
Definition html.hpp:1134
stdex::html::sequence::type
stdex::parser::html_sequence_t type
Sequence type. Enum is used for performance reasons (vs. dynamic_cast)
Definition html.hpp:1133
stdex::html::sequence::parent
sequence * parent
Parent sequence.
Definition html.hpp:1135
stdex::html::starting_token
Token representing start HTML tag.
Definition html.hpp:1922
stdex::html::starting_token::end_sequence
stdex::html::sequence * end_sequence
Ending tag sequence.
Definition html.hpp:1940
stdex::html::starting_token::name
std::basic_string< _Elem, _Traits, _Alloc > name
Element name allowing later recreation of ending </tag>
Definition html.hpp:1939
stdex::html::text_token
Token representing part of HTML text.
Definition html.hpp:1897
stdex::html::text_token::mapping
stdex::mapping_vector< size_t > mapping
Mapping between source and text positions.
Definition html.hpp:1914
stdex::html::text_token::text
std::basic_string< _Elem, _Traits, _Alloc > text
Token text.
Definition html.hpp:1912
stdex::html::text_token::text_type
uint32_t text_type
Mask of text_type_flag_t to specify text content.
Definition html.hpp:1913
stdex::html::token
HTML token base class.
Definition html.hpp:1806
stdex::html::token::sequence
sequence * sequence
Pointer to the sequence this token represents or nullptr when it doesn't trivially represent one sequ...
Definition html.hpp:1875
stdex::html::token::append_tag
size_t append_tag(std::basic_string< char, _Traits, _Alloc > &str) const
Appends token tag to the source code.
Definition html.hpp:1828
stdex::html::token::data
uintptr_t data
Any user-supplied data.
Definition html.hpp:1876
stdex::html::token::type
token_t type
Token type.
Definition html.hpp:1874
stdex::html::token::append_tag
size_t append_tag(std::basic_string< wchar_t, _Traits, _Alloc > &str) const
Appends token tag to the source code.
Definition html.hpp:1844
stdex::html::url_token
HTTP token representing an URL.
Definition html.hpp:1957
stdex::html::url_token::encoding
token_url_t encoding
URL encoding.
Definition html.hpp:1972
stdex::html::url_token::url
std::basic_string< _Elem, _Traits, _Alloc > url
URL.
Definition html.hpp:1971
stdex::parser::basic_css_import::content
stdex::interval< size_t > content
content position in source
Definition parser.hpp:7833
stdex::parser::basic_css_uri::content
stdex::interval< size_t > content
content position in source
Definition parser.hpp:7748
stdex::parser::basic_html_tag::attributes
std::vector< html_attribute > attributes
tag attributes
Definition parser.hpp:8355
stdex::parser::basic_html_tag::type
html_sequence_t type
tag type
Definition parser.hpp:8353
stdex::parser::basic_html_tag::name
stdex::interval< size_t > name
tag name position in source
Definition parser.hpp:8354
stdex::parser::basic_parser::interval
stdex::interval< size_t > interval
Region of the last match.
Definition parser.hpp:172
stdex::parser::basic_string
Test for given string.
Definition parser.hpp:818
stdex::progress
Progress indicator base class.
Definition progress.hpp:19
stdex::progress::cancel
virtual bool cancel()
Query whether user requested abort.
Definition progress.hpp:65
stdex::progress::set
virtual void set(T value)
Set current progress.
Definition progress.hpp:47
stdex::progress::set_range
virtual void set_range(T start, T end)
Set progress range extent.
Definition progress.hpp:37
stdex::user_cancelled
User cancelled exception.
Definition exception.hpp:17
stdex::html::element_traits
Describes attributes associated with a HTML element.
Definition html.hpp:515
stdex::html::element_traits::is_group
static bool is_group(element_t code)
Does element represent a separate part of text?
Definition html.hpp:920
stdex::html::element_traits::is_flow
static bool is_flow(element_t code)
Does element typically represent text?
Definition html.hpp:834
stdex::html::element_traits::is_heading
static bool is_heading(element_t code)
Does element represent a heading?
Definition html.hpp:752
stdex::html::element_traits::is_head_content
static bool is_head_content(element_t code)
Is element part of the document head?
Definition html.hpp:844
stdex::html::element_traits::is_fontstyle
static bool is_fontstyle(element_t code)
Does element represent font styling?
Definition html.hpp:640
stdex::html::element_traits::is_block
static bool is_block(element_t code)
Is element typically displayed as a stand-alone section of text?
Definition html.hpp:803
stdex::html::element_traits::is_head_misc
static bool is_head_misc(element_t code)
May element be a part of document head?
Definition html.hpp:861
stdex::html::element_traits::is_list
static bool is_list(element_t code)
Does element represent a list of items?
Definition html.hpp:771
stdex::html::element_traits::is_uri
static bool is_uri(element_t code, _In_reads_or_z_opt_(num_chars) const T *attr_name, size_t num_chars)
Checks if expected element attribute value is URI.
Definition html.hpp:1050
stdex::html::element_traits::is_preformatted
static bool is_preformatted(element_t code)
Does element represent preformatted text, source code etc.?
Definition html.hpp:788
stdex::html::element_traits::is_localizable
static bool is_localizable(element_t code, const T *attr_name, size_t num_chars)
Checks if expected element attribute value is localizable.
Definition html.hpp:1105
stdex::html::element_traits::is_special
static bool is_special(element_t code)
Does element represent non-textual item in the document?
Definition html.hpp:686
stdex::html::element_traits::is_pre_exclusion
static bool is_pre_exclusion(element_t code)
May element be a part of.
Definition html.hpp:879
stdex::html::element_traits::is_inline
static bool is_inline(element_t code)
Is element typically displayed inline with text?
Definition html.hpp:737
stdex::html::element_traits::is_html_content
static bool is_html_content(element_t code)
Does element represent the document body?
Definition html.hpp:904
stdex::html::element_traits::is_formctrl
static bool is_formctrl(element_t code)
Does element represent a form control?
Definition html.hpp:719
stdex::html::element_traits::is_phrase
static bool is_phrase(element_t code)
Does element represent a phrase-of-speech?
Definition html.hpp:662
stdex::html::element_traits::may_contain
static bool may_contain(element_t parent, element_t child)
Checks if one element may nest inside another.
Definition html.hpp:950
stdex::html::element_traits::span
static element_span_t span(element_t code)
Returns expected element span in HTML code.
Definition html.hpp:521
stdex::html::entity
HTML entity.
Definition html.hpp:1416
stdex::html::entity::name
stdex::interval< size_t > name
Name position in source.
Definition html.hpp:1417
stdex::html::entity::value
std::basic_string< _Elem, _Traits, _Alloc > value
Entity value.
Definition html.hpp:1418
stdex::html::inserted_token
Inserted HTML token.
Definition html.hpp:1978
stdex::html::inserted_token::after_word
bool after_word
true if token is anchored after the word; false if anchored before the word
Definition html.hpp:1982
stdex::html::inserted_token::active_tokens
std::list< stdex::html::token * > active_tokens
List of started tokens at inserted token.
Definition html.hpp:1980
stdex::html::inserted_token::word_index
size_t word_index
Index of the word, token is anchored to.
Definition html.hpp:1981
stdex::html::inserted_token::token
token * token
Points to the token.
Definition html.hpp:1979
stdex::interval
Numerical interval.
Definition interval.hpp:18
stdex::interval::size
T size() const
Returns interval size.
Definition interval.hpp:47
stdex::interval::end
T end
interval end
Definition interval.hpp:20
stdex::interval::start
T start
interval start
Definition interval.hpp:19
stdex::mapping
Maps index in source string to index in destination string.
Definition mapping.hpp:17
stdex::mapping::mapping
mapping()
Constructs a zero to zero mapping.
Definition mapping.hpp:24
stdex::parser::html_attribute
Tag attribute.
Definition parser.hpp:8127
stdex::parser::html_attribute::value
stdex::interval< size_t > value
attribute value position in source
Definition parser.hpp:8129