summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.clang-format120
-rw-r--r--.github/workflows/build.yaml89
-rw-r--r--.github/workflows/static-analysis.yaml61
-rw-r--r--Makefile8
-rw-r--r--bindings/xml/libxml_xmlparser.c265
-rw-r--r--bindings/xml/xmlerror.h4
-rw-r--r--examples/dom-structure-dump.c162
-rw-r--r--examples/example.mk (renamed from examples/makefile)6
-rw-r--r--include/dom/core/tokenlist.h43
-rw-r--r--include/dom/dom.h1
-rw-r--r--include/dom/html/html_elements.h9
-rw-r--r--include/dom/walk.h65
-rw-r--r--src/core/Makefile2
-rw-r--r--src/core/element.c14
-rw-r--r--src/core/tokenlist.c547
-rw-r--r--src/core/tokenlist.h18
-rw-r--r--src/html/html_document.c27
-rw-r--r--src/html/html_element.c6
-rw-r--r--src/utils/Makefile2
-rw-r--r--src/utils/walk.c130
-rw-r--r--test/testutils/utils.c2
21 files changed, 1412 insertions, 169 deletions
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..75df82a
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,120 @@
+---
+Language: Cpp
+AccessModifierOffset: -2
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlines: Right
+AlignOperands: true
+AlignTrailingComments: false
+AllowAllParametersOfDeclarationOnNextLine: false
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: None
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: MultiLine
+BinPackArguments: false
+BinPackParameters: false
+BraceWrapping:
+ AfterClass: true
+ AfterControlStatement: false
+ AfterEnum: false
+ AfterFunction: true
+ AfterNamespace: true
+ AfterObjCDeclaration: false
+ AfterStruct: false
+ AfterUnion: false
+ AfterExternBlock: false
+ BeforeCatch: false
+ BeforeElse: false
+ IndentBraces: false
+ SplitEmptyFunction: true
+ SplitEmptyRecord: true
+ SplitEmptyNamespace: true
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Linux
+BreakBeforeInheritanceComma: false
+BreakInheritanceList: BeforeColon
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializersBeforeComma: false
+BreakConstructorInitializers: BeforeColon
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: false
+ColumnLimit: 80
+CommentPragmas: '^ IWYU pragma:'
+CompactNamespaces: false
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+ConstructorInitializerIndentWidth: 8
+ContinuationIndentWidth: 8
+Cpp11BracedListStyle: true
+DerivePointerAlignment: false
+DisableFormat: false
+ExperimentalAutoDetectBinPacking: false
+FixNamespaceComments: true
+ForEachMacros:
+ - foreach
+ - Q_FOREACH
+ - BOOST_FOREACH
+IncludeBlocks: Preserve
+IncludeCategories:
+ - Regex: '^(<.*/)'
+ Priority: 3
+ - Regex: '^(<(nsutils)/)'
+ Priority: 2
+ - Regex: '"utils/'
+ Priority: 4
+ - Regex: '"netsurf/'
+ Priority: 5
+ - Regex: '.*'
+ Priority: 6
+IncludeIsMainRegex: '(Test)?$'
+IndentCaseLabels: false
+IndentPPDirectives: None
+IndentWidth: 8
+IndentWrappedFunctionNames: false
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: true
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 2
+NamespaceIndentation: None
+ObjCBinPackProtocolList: Auto
+ObjCBlockIndentWidth: 2
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakAssignment: 100
+PenaltyBreakBeforeFirstCallParameter: 50
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakString: 1000
+PenaltyBreakTemplateDeclaration: 10
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 19
+PointerAlignment: Right
+ReflowComments: true
+SortIncludes: false
+SortUsingDeclarations: true
+SpaceAfterCStyleCast: false
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCpp11BracedList: false
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: ControlStatements
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: false
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard: Cpp11
+TabWidth: 8
+UseTab: Always
+...
+
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
new file mode 100644
index 0000000..17f2958
--- /dev/null
+++ b/.github/workflows/build.yaml
@@ -0,0 +1,89 @@
+name: "Linux Build"
+
+on: [push]
+
+jobs:
+ linux:
+ name: '${{ matrix.os }}: ${{ matrix.compiler.vendor }}'
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os:
+ - ubuntu-22.04
+ - ubuntu-20.04
+ compiler:
+ # The NetSurf build system can't find LLVM AR (it looks for it
+ # in /usr/lib instead of /usr/bin:
+ # `make: /usr/lib/llvm-ar: No such file or directory`).
+ # So we need to make it explicit for llvm.
+ - { vendor: gnu, CC: gcc, AR: ar }
+ - { vendor: llvm, CC: clang, AR: llvm-ar }
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 1
+
+ - name: apt-get install packages
+ run: sudo apt-get update -qq &&
+ sudo apt-get install --no-install-recommends -y
+ bison
+ build-essential
+ check
+ clang
+ flex
+ git
+ gperf
+ llvm
+ pkg-config
+ libxml-perl
+ libxml-sax-perl
+ libxml-sax-base-perl
+ libxml-sax-expat-perl
+ libxml-parser-perl
+ libxml-libxml-perl
+ libxml-xpath-perl
+ libswitch-perl
+
+ - name: Get env.sh
+ run: |
+ mkdir projects
+ wget -O projects/env.sh https://git.netsurf-browser.org/netsurf.git/plain/docs/env.sh
+
+ - name: Build and install project deps
+ env:
+ CC: ${{ matrix.compiler.CC }}
+ AR: ${{ matrix.compiler.AR }}
+ TARGET: ${{ github.event.repository.name }}
+ run: |
+ export TARGET_WORKSPACE="$(pwd)/projects"
+ source projects/env.sh
+ ns-clone -d -s
+ ns-make-libs install
+
+ - name: Build Library
+ env:
+ CC: ${{ matrix.compiler.CC }}
+ AR: ${{ matrix.compiler.AR }}
+ TARGET: ${{ github.event.repository.name }}
+ run: |
+ export TARGET_WORKSPACE="$(pwd)/projects"
+ source projects/env.sh
+ make -j"$(nproc)"
+
+ - name: Unit Tests
+ # Planet saving measure:
+ # The tests take ages to convert from XML and we don't gain much by
+ # running them on every combination in the matrix.
+ # Another option might be to have the transformed C tests checked in.
+ if: ${{ matrix.compiler.vendor == 'gnu' && matrix.os == 'ubuntu-22.04' }}
+ env:
+ CC: ${{ matrix.compiler.CC }}
+ AR: ${{ matrix.compiler.AR }}
+ TARGET: ${{ github.event.repository.name }}
+ run: |
+ export TARGET_WORKSPACE="$(pwd)/projects"
+ source projects/env.sh
+ make test
diff --git a/.github/workflows/static-analysis.yaml b/.github/workflows/static-analysis.yaml
new file mode 100644
index 0000000..ec5171f
--- /dev/null
+++ b/.github/workflows/static-analysis.yaml
@@ -0,0 +1,61 @@
+name: "Static Analysis"
+
+on: [push]
+
+jobs:
+ codeql:
+ name: codeql
+ runs-on: ubuntu-22.04
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: ['cpp']
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v2
+ with:
+ fetch-depth: 1
+
+ - name: apt-get install packages
+ run: sudo apt-get update -qq &&
+ sudo apt-get install --no-install-recommends -y
+ bison
+ build-essential
+ check
+ clang
+ flex
+ git
+ gperf
+ llvm
+ pkg-config
+
+ - name: Get env.sh
+ run: |
+ mkdir projects
+ wget -O projects/env.sh https://git.netsurf-browser.org/netsurf.git/plain/docs/env.sh
+
+ - name: Build and install project deps
+ env:
+ TARGET: ${{ github.event.repository.name }}
+ run: |
+ export TARGET_WORKSPACE="$(pwd)/projects"
+ source projects/env.sh
+ ns-clone -d -s
+ ns-make-libs install
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v2
+ with:
+ languages: ${{ matrix.language }}
+
+ - name: Build Library
+ run: |
+ export TARGET_WORKSPACE="$(pwd)/projects"
+ source projects/env.sh
+ make -j"$(nproc)"
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v2
diff --git a/Makefile b/Makefile
index 5690403..f542630 100644
--- a/Makefile
+++ b/Makefile
@@ -6,7 +6,7 @@
# Component settings
COMPONENT := dom
-COMPONENT_VERSION := 0.4.1
+COMPONENT_VERSION := 0.4.2
# Default to a static library
COMPONENT_TYPE ?= lib-static
@@ -56,7 +56,7 @@ include $(NSBUILD)/Makefile.top
# Extra installation rules
Is := include/dom
I := /$(INCLUDEDIR)/dom
-INSTALL_ITEMS := $(INSTALL_ITEMS) $(I):$(Is)/dom.h;$(Is)/functypes.h;$(Is)/inttypes.h
+INSTALL_ITEMS := $(INSTALL_ITEMS) $(I):$(Is)/dom.h;$(Is)/functypes.h;$(Is)/inttypes.h;$(Is)/walk.h
Is := include/dom/core
I := /$(INCLUDEDIR)/dom/core
@@ -69,7 +69,7 @@ INSTALL_ITEMS := $(INSTALL_ITEMS) $(I):$(Is)/entity_ref.h
INSTALL_ITEMS := $(INSTALL_ITEMS) $(I):$(Is)/element.h;$(Is)/exceptions.h
INSTALL_ITEMS := $(INSTALL_ITEMS) $(I):$(Is)/implementation.h
INSTALL_ITEMS := $(INSTALL_ITEMS) $(I):$(Is)/namednodemap.h;$(Is)/node.h
-INSTALL_ITEMS := $(INSTALL_ITEMS) $(I):$(Is)/nodelist.h;$(Is)/string.h
+INSTALL_ITEMS := $(INSTALL_ITEMS) $(I):$(Is)/nodelist.h;$(Is)/tokenlist.h;$(Is)/string.h
INSTALL_ITEMS := $(INSTALL_ITEMS) $(I):$(Is)/pi.h
INSTALL_ITEMS := $(INSTALL_ITEMS) $(I):$(Is)/text.h;$(Is)/typeinfo.h
@@ -149,6 +149,8 @@ INSTALL_ITEMS := $(INSTALL_ITEMS) $(I):$(Is)/html_isindex_element.h
INSTALL_ITEMS := $(INSTALL_ITEMS) /$(LIBDIR)/pkgconfig:lib$(COMPONENT).pc.in
INSTALL_ITEMS := $(INSTALL_ITEMS) /$(LIBDIR):$(OUTPUT)
+REQUIRED_PKGS := $(REQUIRED_PKGS) libwapcaplet
+
ifeq ($(WITH_LIBXML_BINDING),yes)
REQUIRED_PKGS := $(REQUIRED_PKGS) libxml-2.0
endif
diff --git a/bindings/xml/libxml_xmlparser.c b/bindings/xml/libxml_xmlparser.c
index 02b8a34..cd7f8ad 100644
--- a/bindings/xml/libxml_xmlparser.c
+++ b/bindings/xml/libxml_xmlparser.c
@@ -6,6 +6,7 @@
*/
#include <stdbool.h>
+#include <stdlib.h>
#include <string.h>
#include <assert.h>
@@ -36,21 +37,21 @@ static void xml_parser_end_element_ns(void *ctx, const xmlChar *localname,
static dom_exception xml_parser_link_nodes(dom_xml_parser *parser,
struct dom_node *dom, xmlNodePtr xml);
-static void xml_parser_add_node(dom_xml_parser *parser, struct dom_node *parent,
- xmlNodePtr child);
-static void xml_parser_add_element_node(dom_xml_parser *parser,
+static dom_exception xml_parser_add_node(dom_xml_parser *parser,
struct dom_node *parent, xmlNodePtr child);
-static void xml_parser_add_text_node(dom_xml_parser *parser,
+static dom_exception xml_parser_add_element_node(dom_xml_parser *parser,
struct dom_node *parent, xmlNodePtr child);
-static void xml_parser_add_cdata_section(dom_xml_parser *parser,
+static dom_exception xml_parser_add_text_node(dom_xml_parser *parser,
struct dom_node *parent, xmlNodePtr child);
-static void xml_parser_add_entity_reference(dom_xml_parser *parser,
+static dom_exception xml_parser_add_cdata_section(dom_xml_parser *parser,
struct dom_node *parent, xmlNodePtr child);
-static void xml_parser_add_entity(dom_xml_parser *parser,
+static dom_exception xml_parser_add_entity_reference(dom_xml_parser *parser,
+ struct dom_node *parent, xmlNodePtr child);
+static dom_exception xml_parser_add_entity(dom_xml_parser *parser,
struct dom_node *parent, xmlNodePtr child);
-static void xml_parser_add_comment(dom_xml_parser *parser,
+static dom_exception xml_parser_add_comment(dom_xml_parser *parser,
struct dom_node *parent, xmlNodePtr child);
-static void xml_parser_add_document_type(dom_xml_parser *parser,
+static dom_exception xml_parser_add_document_type(dom_xml_parser *parser,
struct dom_node *parent, xmlNodePtr child);
static void xml_parser_internal_subset(void *ctx, const xmlChar *name,
@@ -96,6 +97,8 @@ struct dom_xml_parser {
dom_msg msg; /**< Informational message function */
void *mctx; /**< Pointer to client data */
+
+ dom_exception err; /**< Last DOM error, if any */
};
/**
@@ -133,7 +136,7 @@ static xmlSAXHandler sax_handler = {
._private = NULL,
.startElementNs = xml_parser_start_element_ns,
.endElementNs = xml_parser_end_element_ns,
- .serror = NULL
+ .serror = NULL,
};
static void *dom_xml_alloc(void *ptr, size_t len, void *pw)
@@ -231,6 +234,8 @@ dom_xml_parser *dom_xml_parser_create(const char *enc, const char *int_enc,
parser->msg = msg;
parser->mctx = mctx;
+ parser->err = DOM_NO_ERR;
+
return parser;
}
@@ -271,6 +276,10 @@ dom_xml_error dom_xml_parser_parse_chunk(dom_xml_parser *parser,
return DOM_XML_EXTERNAL_ERR | err;
}
+ if (parser->err != DOM_NO_ERR) {
+ return DOM_XML_DOM_ERR | parser->err;
+ }
+
return DOM_XML_OK;
}
@@ -293,6 +302,10 @@ dom_xml_error dom_xml_parser_completed(dom_xml_parser *parser)
return DOM_XML_EXTERNAL_ERR | err;
}
+ if (parser->err != DOM_NO_ERR) {
+ return DOM_XML_DOM_ERR | parser->err;
+ }
+
return DOM_XML_OK;
}
@@ -304,15 +317,27 @@ dom_xml_error dom_xml_parser_completed(dom_xml_parser *parser)
void xml_parser_start_document(void *ctx)
{
dom_xml_parser *parser = (dom_xml_parser *) ctx;
- dom_exception err;
+#if LIBXML_VERSION >= 21200
+ const xmlError *xmlerr;
+#else
+ xmlErrorPtr xmlerr;
+#endif
+
+ if (parser->err != DOM_NO_ERR)
+ return;
/* Invoke libxml2's default behaviour */
xmlSAX2StartDocument(parser->xml_ctx);
+ xmlerr = xmlCtxtGetLastError(parser->xml_ctx);
+ if (xmlerr != NULL && xmlerr->level >= XML_ERR_ERROR) {
+ return;
+ }
/* Link nodes together */
- err = xml_parser_link_nodes(parser, (struct dom_node *) parser->doc,
+ parser->err = xml_parser_link_nodes(parser,
+ (struct dom_node *) parser->doc,
(xmlNodePtr) parser->xml_ctx->myDoc);
- if (err != DOM_NO_ERR) {
+ if (parser->err != DOM_NO_ERR) {
parser->msg(DOM_MSG_WARNING, parser->mctx,
"Not able to link document nodes");
}
@@ -328,10 +353,21 @@ void xml_parser_end_document(void *ctx)
dom_xml_parser *parser = (dom_xml_parser *) ctx;
xmlNodePtr node;
xmlNodePtr n;
- dom_exception err;
+#if LIBXML_VERSION >= 21200
+ const xmlError *xmlerr;
+#else
+ xmlErrorPtr xmlerr;
+#endif
+
+ if (parser->err != DOM_NO_ERR)
+ return;
/* Invoke libxml2's default behaviour */
xmlSAX2EndDocument(parser->xml_ctx);
+ xmlerr = xmlCtxtGetLastError(parser->xml_ctx);
+ if (xmlerr != NULL && xmlerr->level >= XML_ERR_ERROR) {
+ return;
+ }
/* If there is no document, we can't do anything */
if (parser->doc == NULL) {
@@ -344,9 +380,13 @@ void xml_parser_end_document(void *ctx)
* children which occur after the last Element node in the list */
/* Get XML node */
- err = dom_node_get_user_data((struct dom_node *) parser->doc,
+ parser->err = dom_node_get_user_data((struct dom_node *) parser->doc,
parser->udkey, (void **) (void *) &node);
- if (err != DOM_NO_ERR) {
+
+ /* The return value from dom_node_get_user_data() is always
+ * DOM_NO_ERR, but the returned "node" will be NULL if no user
+ * data is found. */
+ if (parser->err != DOM_NO_ERR || node == NULL) {
parser->msg(DOM_MSG_WARNING, parser->mctx,
"Failed finding XML node");
return;
@@ -368,8 +408,10 @@ void xml_parser_end_document(void *ctx)
/* Now, mirror nodes in the DOM */
for (; n != NULL; n = n->next) {
- xml_parser_add_node(parser,
+ parser->err = xml_parser_add_node(parser,
(struct dom_node *) node->_private, n);
+ if (parser->err != DOM_NO_ERR)
+ return;
}
}
@@ -397,11 +439,23 @@ void xml_parser_start_element_ns(void *ctx, const xmlChar *localname,
{
dom_xml_parser *parser = (dom_xml_parser *) ctx;
xmlNodePtr parent = parser->xml_ctx->node;
+#if LIBXML_VERSION >= 21200
+ const xmlError *xmlerr;
+#else
+ xmlErrorPtr xmlerr;
+#endif
+
+ if (parser->err != DOM_NO_ERR)
+ return;
/* Invoke libxml2's default behaviour */
xmlSAX2StartElementNs(parser->xml_ctx, localname, prefix, URI,
nb_namespaces, namespaces, nb_attributes,
nb_defaulted, attributes);
+ xmlerr = xmlCtxtGetLastError(parser->xml_ctx);
+ if (xmlerr != NULL && xmlerr->level >= XML_ERR_ERROR) {
+ return;
+ }
/* If there is no document, we can't do anything */
if (parser->doc == NULL) {
@@ -440,16 +494,18 @@ void xml_parser_start_element_ns(void *ctx, const xmlChar *localname,
/* Now, mirror nodes in the DOM */
for (; n != parser->xml_ctx->node; n = n->next) {
- xml_parser_add_node(parser,
+ parser->err = xml_parser_add_node(parser,
(struct dom_node *) parent->_private,
n);
+ if (parser->err != DOM_NO_ERR)
+ return;
}
}
/* Mirror the created node and its attributes in the DOM */
- xml_parser_add_node(parser, (struct dom_node *) parent->_private,
+ parser->err = xml_parser_add_node(parser,
+ (struct dom_node *) parent->_private,
parser->xml_ctx->node);
-
}
/**
@@ -466,9 +522,21 @@ void xml_parser_end_element_ns(void *ctx, const xmlChar *localname,
dom_xml_parser *parser = (dom_xml_parser *) ctx;
xmlNodePtr node = parser->xml_ctx->node;
xmlNodePtr n;
+#if LIBXML_VERSION >= 21200
+ const xmlError *xmlerr;
+#else
+ xmlErrorPtr xmlerr;
+#endif
+
+ if (parser->err != DOM_NO_ERR)
+ return;
/* Invoke libxml2's default behaviour */
xmlSAX2EndElementNs(parser->xml_ctx, localname, prefix, URI);
+ xmlerr = xmlCtxtGetLastError(parser->xml_ctx);
+ if (xmlerr != NULL && xmlerr->level >= XML_ERR_ERROR) {
+ return;
+ }
/* If there is no document, we can't do anything */
if (parser->doc == NULL) {
@@ -503,8 +571,10 @@ void xml_parser_end_element_ns(void *ctx, const xmlChar *localname,
/* Now, mirror nodes in the DOM */
for (; n != NULL; n = n->next) {
- xml_parser_add_node(parser,
+ parser->err = xml_parser_add_node(parser,
(struct dom_node *) node->_private, n);
+ if (parser->err != DOM_NO_ERR)
+ return;
}
}
@@ -543,9 +613,10 @@ dom_exception xml_parser_link_nodes(dom_xml_parser *parser,
* \param parser The parser context
* \param parent The parent DOM node
* \param child The xmlNode to mirror in the DOM as a child of parent
+ * \return DOM_NO_ERR on success, appropriate error otherwise
*/
-void xml_parser_add_node(dom_xml_parser *parser, struct dom_node *parent,
- xmlNodePtr child)
+dom_exception xml_parser_add_node(dom_xml_parser *parser,
+ struct dom_node *parent, xmlNodePtr child)
{
static const char *node_types[] = {
"THIS_IS_NOT_A_NODE",
@@ -574,31 +645,26 @@ void xml_parser_add_node(dom_xml_parser *parser, struct dom_node *parent,
switch (child->type) {
case XML_ELEMENT_NODE:
- xml_parser_add_element_node(parser, parent, child);
- break;
+ return xml_parser_add_element_node(parser, parent, child);
case XML_TEXT_NODE:
- xml_parser_add_text_node(parser, parent, child);
- break;
+ return xml_parser_add_text_node(parser, parent, child);
case XML_CDATA_SECTION_NODE:
- xml_parser_add_cdata_section(parser, parent, child);
- break;
+ return xml_parser_add_cdata_section(parser, parent, child);
case XML_ENTITY_REF_NODE:
- xml_parser_add_entity_reference(parser, parent, child);
- break;
+ return xml_parser_add_entity_reference(parser, parent, child);
case XML_COMMENT_NODE:
- xml_parser_add_comment(parser, parent, child);
- break;
+ return xml_parser_add_comment(parser, parent, child);
case XML_DTD_NODE:
- xml_parser_add_document_type(parser, parent, child);
- break;
- case XML_ENTITY_DECL:
- xml_parser_add_entity(parser, parent, child);
- break;
+ return xml_parser_add_document_type(parser, parent, child);
+ case XML_ENTITY_DECL:
+ return xml_parser_add_entity(parser, parent, child);
default:
parser->msg(DOM_MSG_NOTICE, parser->mctx,
"Unsupported node type: %s",
node_types[child->type]);
}
+
+ return DOM_NO_ERR;
}
/**
@@ -607,8 +673,9 @@ void xml_parser_add_node(dom_xml_parser *parser, struct dom_node *parent,
* \param parser The parser context
* \param parent The parent DOM node
* \param child The xmlNode to mirror in the DOM as a child of parent
+ * \return DOM_NO_ERR on success, appropriate error otherwise
*/
-void xml_parser_add_element_node(dom_xml_parser *parser,
+dom_exception xml_parser_add_element_node(dom_xml_parser *parser,
struct dom_node *parent, xmlNodePtr child)
{
struct dom_element *el, *ins_el = NULL;
@@ -626,7 +693,7 @@ void xml_parser_add_element_node(dom_xml_parser *parser,
if (err != DOM_NO_ERR) {
parser->msg(DOM_MSG_CRITICAL, parser->mctx,
"No memory for tag name");
- return;
+ return err;
}
/* Create element node */
@@ -637,7 +704,7 @@ void xml_parser_add_element_node(dom_xml_parser *parser,
parser->msg(DOM_MSG_CRITICAL, parser->mctx,
"Failed creating element '%s'",
child->name);
- return;
+ return err;
}
/* No longer need tag name */
@@ -660,7 +727,7 @@ void xml_parser_add_element_node(dom_xml_parser *parser,
if (err != DOM_NO_ERR) {
parser->msg(DOM_MSG_CRITICAL, parser->mctx,
"No memory for namespace");
- return;
+ return err;
}
/* QName is "prefix:localname",
@@ -680,7 +747,7 @@ void xml_parser_add_element_node(dom_xml_parser *parser,
dom_string_unref(namespace);
parser->msg(DOM_MSG_CRITICAL, parser->mctx,
"No memory for qname");
- return;
+ return err;
}
/* Create element node */
@@ -692,7 +759,7 @@ void xml_parser_add_element_node(dom_xml_parser *parser,
parser->msg(DOM_MSG_CRITICAL, parser->mctx,
"Failed creating element '%s'",
qnamebuf);
- return;
+ return err;
}
/* No longer need namespace / qname */
@@ -752,7 +819,7 @@ void xml_parser_add_element_node(dom_xml_parser *parser,
if (err != DOM_NO_ERR) {
parser->msg(DOM_MSG_CRITICAL, parser->mctx,
"No memory for namespace");
- return;
+ goto cleanup;
}
/* QName is "prefix:localname",
@@ -772,7 +839,7 @@ void xml_parser_add_element_node(dom_xml_parser *parser,
dom_string_unref(namespace);
parser->msg(DOM_MSG_CRITICAL, parser->mctx,
"No memory for qname");
- return;
+ goto cleanup;
}
/* Create attribute */
@@ -784,7 +851,7 @@ void xml_parser_add_element_node(dom_xml_parser *parser,
parser->msg(DOM_MSG_CRITICAL, parser->mctx,
"Failed creating attribute \
'%s'", qnamebuf);
- return;
+ goto cleanup;
}
/* No longer need namespace / qname */
@@ -794,8 +861,12 @@ void xml_parser_add_element_node(dom_xml_parser *parser,
/* Clone subtree (attribute value) */
for (c = a->children; c != NULL; c = c->next) {
- xml_parser_add_node(parser,
+ err = xml_parser_add_node(parser,
(struct dom_node *) attr, c);
+ if (err != DOM_NO_ERR) {
+ dom_node_unref((struct dom_node *) attr);
+ goto cleanup;
+ }
}
/* Link nodes together */
@@ -861,14 +932,14 @@ void xml_parser_add_element_node(dom_xml_parser *parser,
/* No longer interested in element node */
dom_node_unref((struct dom_node *) el);
- return;
+ return DOM_NO_ERR;
cleanup:
/* No longer want node (any attributes attached to it
* will be cleaned up with it) */
dom_node_unref((struct dom_node *) el);
- return;
+ return err;
}
/**
@@ -877,9 +948,10 @@ cleanup:
* \param parser The parser context
* \param parent The parent DOM node
* \param child The xmlNode to mirror in the DOM as a child of parent
+ * \return DOM_NO_ERR on success, appropriate error otherwise
*/
-void xml_parser_add_text_node(dom_xml_parser *parser, struct dom_node *parent,
- xmlNodePtr child)
+dom_exception xml_parser_add_text_node(dom_xml_parser *parser,
+ struct dom_node *parent, xmlNodePtr child)
{
struct dom_text *text, *ins_text = NULL;
dom_string *data;
@@ -891,7 +963,7 @@ void xml_parser_add_text_node(dom_xml_parser *parser, struct dom_node *parent,
if (err != DOM_NO_ERR) {
parser->msg(DOM_MSG_CRITICAL, parser->mctx,
"No memory for text node contents ");
- return;
+ return err;
}
/* Create text node */
@@ -900,7 +972,7 @@ void xml_parser_add_text_node(dom_xml_parser *parser, struct dom_node *parent,
dom_string_unref(data);
parser->msg(DOM_MSG_CRITICAL, parser->mctx,
"No memory for text node");
- return;
+ return err;
}
/* No longer need data */
@@ -913,7 +985,7 @@ void xml_parser_add_text_node(dom_xml_parser *parser, struct dom_node *parent,
dom_node_unref((struct dom_node *) text);
parser->msg(DOM_MSG_ERROR, parser->mctx,
"Failed attaching text node");
- return;
+ return err;
}
/* We're not interested in the inserted text node */
@@ -925,11 +997,13 @@ void xml_parser_add_text_node(dom_xml_parser *parser, struct dom_node *parent,
child);
if (err != DOM_NO_ERR) {
dom_node_unref((struct dom_node *) text);
- return;
+ return err;
}
/* No longer interested in text node */
dom_node_unref((struct dom_node *) text);
+
+ return DOM_NO_ERR;
}
/**
@@ -938,8 +1012,9 @@ void xml_parser_add_text_node(dom_xml_parser *parser, struct dom_node *parent,
* \param parser The parser context
* \param parent The parent DOM node
* \param child The xmlNode to mirror in the DOM as a child of parent
+ * \return DOM_NO_ERR on success, appropriate error otherwise
*/
-void xml_parser_add_cdata_section(dom_xml_parser *parser,
+dom_exception xml_parser_add_cdata_section(dom_xml_parser *parser,
struct dom_node *parent, xmlNodePtr child)
{
struct dom_cdata_section *cdata, *ins_cdata = NULL;
@@ -952,7 +1027,7 @@ void xml_parser_add_cdata_section(dom_xml_parser *parser,
if (err != DOM_NO_ERR) {
parser->msg(DOM_MSG_CRITICAL, parser->mctx,
"No memory for cdata section contents");
- return;
+ return err;
}
/* Create cdata section */
@@ -961,7 +1036,7 @@ void xml_parser_add_cdata_section(dom_xml_parser *parser,
dom_string_unref(data);
parser->msg(DOM_MSG_CRITICAL, parser->mctx,
"No memory for cdata section");
- return;
+ return err;
}
/* No longer need data */
@@ -974,7 +1049,7 @@ void xml_parser_add_cdata_section(dom_xml_parser *parser,
dom_node_unref((struct dom_node *) cdata);
parser->msg(DOM_MSG_ERROR, parser->mctx,
"Failed attaching cdata section");
- return;
+ return err;
}
/* We're not interested in the inserted cdata section */
@@ -986,11 +1061,13 @@ void xml_parser_add_cdata_section(dom_xml_parser *parser,
child);
if (err != DOM_NO_ERR) {
dom_node_unref((struct dom_node *) cdata);
- return;
+ return err;
}
/* No longer interested in cdata section */
dom_node_unref((struct dom_node *) cdata);
+
+ return DOM_NO_ERR;
}
/**
@@ -999,8 +1076,9 @@ void xml_parser_add_cdata_section(dom_xml_parser *parser,
* \param parser The parser context
* \param parent The parent DOM node
* \param child The xmlNode to mirror in the DOM as a child of parent
+ * \return DOM_NO_ERR on success, appropriate error otherwise
*/
-void xml_parser_add_entity_reference(dom_xml_parser *parser,
+dom_exception xml_parser_add_entity_reference(dom_xml_parser *parser,
struct dom_node *parent, xmlNodePtr child)
{
struct dom_entity_reference *entity, *ins_entity = NULL;
@@ -1014,7 +1092,7 @@ void xml_parser_add_entity_reference(dom_xml_parser *parser,
if (err != DOM_NO_ERR) {
parser->msg(DOM_MSG_CRITICAL, parser->mctx,
"No memory for entity reference name");
- return;
+ return err;
}
/* Create text node */
@@ -1024,7 +1102,7 @@ void xml_parser_add_entity_reference(dom_xml_parser *parser,
dom_string_unref(name);
parser->msg(DOM_MSG_CRITICAL, parser->mctx,
"No memory for entity reference");
- return;
+ return err;
}
/* No longer need name */
@@ -1032,7 +1110,10 @@ void xml_parser_add_entity_reference(dom_xml_parser *parser,
/* Mirror subtree (reference value) */
for (c = child->children; c != NULL; c = c->next) {
- xml_parser_add_node(parser, (struct dom_node *) entity, c);
+ err = xml_parser_add_node(parser,
+ (struct dom_node *) entity, c);
+ if (err != DOM_NO_ERR)
+ return err;
}
/* Append entity reference to parent */
@@ -1042,7 +1123,7 @@ void xml_parser_add_entity_reference(dom_xml_parser *parser,
dom_node_unref((struct dom_node *) entity);
parser->msg(DOM_MSG_ERROR, parser->mctx,
"Failed attaching entity reference");
- return;
+ return err;
}
/* We're not interested in the inserted entity reference */
@@ -1054,19 +1135,33 @@ void xml_parser_add_entity_reference(dom_xml_parser *parser,
child);
if (err != DOM_NO_ERR) {
dom_node_unref((struct dom_node *) entity);
- return;
+ return err;
}
/* No longer interested in entity reference */
dom_node_unref((struct dom_node *) entity);
+
+ return DOM_NO_ERR;
}
-static void xml_parser_add_entity(dom_xml_parser *parser,
- struct dom_node *parent, xmlNodePtr child)
+/**
+ * Add an entity to the DOM
+ *
+ * \param parser The parser context
+ * \param parent The parent DOM node
+ * \param child The xmlNode to mirror in the DOM as a child of parent
+ * \return DOM_NO_ERR on success, appropriate error otherwise
+ */
+dom_exception xml_parser_add_entity(dom_xml_parser *parser,
+ struct dom_node *parent, xmlNodePtr child)
{
- UNUSED(parser);
- UNUSED(parent);
- UNUSED(child);
+ UNUSED(parser);
+ UNUSED(parent);
+ UNUSED(child);
+
+ /** \todo implement */
+
+ return DOM_NO_ERR;
}
/**
@@ -1075,9 +1170,10 @@ static void xml_parser_add_entity(dom_xml_parser *parser,
* \param parser The parser context
* \param parent The parent DOM node
* \param child The xmlNode to mirror in the DOM as a child of parent
+ * \return DOM_NO_ERR on success, appropriate error otherwise
*/
-void xml_parser_add_comment(dom_xml_parser *parser, struct dom_node *parent,
- xmlNodePtr child)
+dom_exception xml_parser_add_comment(dom_xml_parser *parser,
+ struct dom_node *parent, xmlNodePtr child)
{
struct dom_comment *comment, *ins_comment = NULL;
dom_string *data;
@@ -1089,7 +1185,7 @@ void xml_parser_add_comment(dom_xml_parser *parser, struct dom_node *parent,
if (err != DOM_NO_ERR) {
parser->msg(DOM_MSG_CRITICAL, parser->mctx,
"No memory for comment data");
- return;
+ return err;
}
/* Create comment */
@@ -1098,7 +1194,7 @@ void xml_parser_add_comment(dom_xml_parser *parser, struct dom_node *parent,
dom_string_unref(data);
parser->msg(DOM_MSG_CRITICAL, parser->mctx,
"No memory for comment node");
- return;
+ return err;
}
/* No longer need data */
@@ -1111,7 +1207,7 @@ void xml_parser_add_comment(dom_xml_parser *parser, struct dom_node *parent,
dom_node_unref((struct dom_node *) comment);
parser->msg(DOM_MSG_CRITICAL, parser->mctx,
"Failed attaching comment node");
- return;
+ return err;
}
/* We're not interested in the inserted comment */
@@ -1123,11 +1219,13 @@ void xml_parser_add_comment(dom_xml_parser *parser, struct dom_node *parent,
child);
if (err != DOM_NO_ERR) {
dom_node_unref((struct dom_node *) comment);
- return;
+ return err;
}
/* No longer interested in comment */
dom_node_unref((struct dom_node *) comment);
+
+ return DOM_NO_ERR;
}
/**
@@ -1136,8 +1234,9 @@ void xml_parser_add_comment(dom_xml_parser *parser, struct dom_node *parent,
* \param parser The parser context
* \param parent The parent DOM node
* \param child The xmlNode to mirror in the DOM as a child of parent
+ * \return DOM_NO_ERR on success, appropriate error otherwise
*/
-void xml_parser_add_document_type(dom_xml_parser *parser,
+dom_exception xml_parser_add_document_type(dom_xml_parser *parser,
struct dom_node *parent, xmlNodePtr child)
{
xmlDtdPtr dtd = (xmlDtdPtr) child;
@@ -1163,7 +1262,7 @@ void xml_parser_add_document_type(dom_xml_parser *parser,
if (err != DOM_NO_ERR) {
parser->msg(DOM_MSG_CRITICAL, parser->mctx,
"Failed to create document type");
- return;
+ return err;
}
/* Add doctype to document */
@@ -1173,7 +1272,7 @@ void xml_parser_add_document_type(dom_xml_parser *parser,
dom_node_unref((struct dom_node *) doctype);
parser->msg(DOM_MSG_CRITICAL, parser->mctx,
"Failed attaching doctype");
- return;
+ return err;
}
/* Not interested in inserted node */
@@ -1185,11 +1284,13 @@ void xml_parser_add_document_type(dom_xml_parser *parser,
child);
if (err != DOM_NO_ERR) {
dom_node_unref((struct dom_node *) doctype);
- return;
+ return err;
}
/* No longer interested in doctype */
dom_node_unref((struct dom_node *) doctype);
+
+ return DOM_NO_ERR;
}
/* ------------------------------------------------------------------------*/
diff --git a/bindings/xml/xmlerror.h b/bindings/xml/xmlerror.h
index 51f2c8b..a6cb905 100644
--- a/bindings/xml/xmlerror.h
+++ b/bindings/xml/xmlerror.h
@@ -13,7 +13,9 @@ typedef enum {
DOM_XML_NOMEM = 1,
- DOM_XML_EXTERNAL_ERR = (1<<16),
+ DOM_XML_EXTERNAL_ERR = (1<<16),
+
+ DOM_XML_DOM_ERR = (1<<24),
} dom_xml_error;
#endif
diff --git a/examples/dom-structure-dump.c b/examples/dom-structure-dump.c
index 4ce7670..189292d 100644
--- a/examples/dom-structure-dump.c
+++ b/examples/dom-structure-dump.c
@@ -44,6 +44,7 @@
#include <string.h>
#include <dom/dom.h>
+#include <dom/walk.h>
#include <dom/bindings/hubbub/parser.h>
@@ -181,6 +182,12 @@ bool dump_dom_element_attribute(dom_node *node, char *attribute)
return true;
}
+static inline void dump_indent(int depth)
+{
+ for (int i = 0; i < depth; i++) {
+ printf(" ");
+ }
+}
/**
* Print a line in a DOM structure dump for an element
@@ -189,25 +196,13 @@ bool dump_dom_element_attribute(dom_node *node, char *attribute)
* \param depth The node's depth
* \return true on success, or false on error
*/
-bool dump_dom_element(dom_node *node, int depth)
+bool dump_dom_element(dom_node *node, int depth, bool close)
{
dom_exception exc;
dom_string *node_name = NULL;
- dom_node_type type;
- int i;
const char *string;
size_t length;
- /* Only interested in element nodes */
- exc = dom_node_get_node_type(node, &type);
- if (exc != DOM_NO_ERR) {
- printf("Exception raised for node_get_node_type\n");
- return false;
- } else if (type != DOM_ELEMENT_NODE) {
- /* Nothing to print */
- return true;
- }
-
/* Get element name */
exc = dom_node_get_node_name(node, &node_name);
if (exc != DOM_NO_ERR) {
@@ -215,48 +210,84 @@ bool dump_dom_element(dom_node *node, int depth)
return false;
} else if (node_name == NULL) {
printf("Broken: root_name == NULL\n");
- return false;
+ return false;
}
/* Print ASCII tree structure for current node */
- if (depth > 0) {
- for (i = 0; i < depth; i++) {
- printf("| ");
- }
- printf("+-");
- }
+ dump_indent(depth);
/* Get string data and print element name */
string = dom_string_data(node_name);
length = dom_string_byte_length(node_name);
- printf("[%.*s]", (int)length, string);
-
- if (length == 5 && strncmp(string, "title", 5) == 0) {
- /* Title tag, gather the title */
- dom_string *str;
- exc = dom_node_get_text_content(node, &str);
- if (exc == DOM_NO_ERR && str != NULL) {
- printf(" $%.*s$", (int)dom_string_byte_length(str),
- dom_string_data(str));
- dom_string_unref(str);
- }
- }
- /* Finished with the node_name dom_string */
+ /* TODO: Some elements don't have close tags; only print close tags for
+ * those that do. */
+ printf("<%s%.*s", close ? "/" : "", (int)length, string);
+
dom_string_unref(node_name);
- /* Print the element's id & class, if it has them */
- if (dump_dom_element_attribute(node, "id") == false ||
- dump_dom_element_attribute(node, "class") == false) {
- /* Error occured */
- printf("\n");
- return false;
+ if (!close) {
+ if (length == 5 && strncmp(string, "title", 5) == 0) {
+ /* Title tag, gather the title */
+ dom_string *s;
+ exc = dom_node_get_text_content(node, &s);
+ if (exc == DOM_NO_ERR && s != NULL) {
+ printf(" $%.*s$",
+ (int)dom_string_byte_length(s),
+ dom_string_data(s));
+ dom_string_unref(s);
+ }
+ }
+
+ /* Print the element's id & class, if it has them */
+ if (dump_dom_element_attribute(node, "id") == false ||
+ dump_dom_element_attribute(node, "class") == false) {
+ /* Error occured */
+ printf(">\n");
+ return false;
+ }
}
- printf("\n");
+ printf(">\n");
return true;
}
+/**
+ * Structure dump callback for DOM walker.
+ */
+enum dom_walk_cmd dump_dom_structure__cb(
+ enum dom_walk_stage stage,
+ dom_node_type type,
+ dom_node *node,
+ void *pw)
+{
+ int *depth = pw;
+
+ switch (type) {
+ case DOM_ELEMENT_NODE:
+ switch (stage) {
+ case DOM_WALK_STAGE_ENTER:
+ (*depth)++;
+ if (!dump_dom_element(node, *depth, false)) {
+ return DOM_WALK_CMD_ABORT;
+ }
+ break;
+
+ case DOM_WALK_STAGE_LEAVE:
+ if (!dump_dom_element(node, *depth, true)) {
+ return DOM_WALK_CMD_ABORT;
+ }
+ (*depth)--;
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return DOM_WALK_CMD_CONTINUE;
+}
/**
* Walk though a DOM (sub)tree, in depth first order, printing DOM structure.
@@ -267,51 +298,34 @@ bool dump_dom_element(dom_node *node, int depth)
bool dump_dom_structure(dom_node *node, int depth)
{
dom_exception exc;
- dom_node *child;
- /* Print this node's entry */
- if (dump_dom_element(node, depth) == false) {
- /* There was an error; return */
+ if (!dump_dom_element(node, depth, false)) {
return false;
}
- /* Get the node's first child */
- exc = dom_node_get_first_child(node, &child);
+ exc = libdom_treewalk(DOM_WALK_ENABLE_ALL,
+ dump_dom_structure__cb,
+ node, &depth);
if (exc != DOM_NO_ERR) {
- printf("Exception raised for node_get_first_child\n");
return false;
- } else if (child != NULL) {
- /* node has children; decend to children's depth */
- depth++;
-
- /* Loop though all node's children */
- do {
- dom_node *next_child;
-
- /* Visit node's descendents */
- if (dump_dom_structure(child, depth) == false) {
- /* There was an error; return */
- dom_node_unref(child);
- return false;
- }
-
- /* Go to next sibling */
- exc = dom_node_get_next_sibling(child, &next_child);
- if (exc != DOM_NO_ERR) {
- printf("Exception raised for "
- "node_get_next_sibling\n");
- dom_node_unref(child);
- return false;
- }
+ }
- dom_node_unref(child);
- child = next_child;
- } while (child != NULL); /* No more children */
+ if (!dump_dom_element(node, depth, true)) {
+ return false;
}
return true;
}
+/* LWC leak callback */
+void sd__fini_lwc_callback(lwc_string *str, void *pw)
+{
+ (void)(pw);
+
+ fprintf(stderr, "Leaked string: %.*s\n",
+ (int)lwc_string_length(str),
+ lwc_string_data(str));
+}
/**
* Main entry point from OS.
@@ -354,6 +368,8 @@ int main(int argc, char **argv)
/* Finished with the dom_document */
dom_node_unref(doc);
+ dom_namespace_finalise();
+ lwc_iterate_strings(sd__fini_lwc_callback, NULL);
return EXIT_SUCCESS;
}
diff --git a/examples/makefile b/examples/example.mk
index 47cc7ae..8e1da17 100644
--- a/examples/makefile
+++ b/examples/example.mk
@@ -1,3 +1,9 @@
+# From the top level:
+#
+# make -C examples -f example.mk clean
+# make -C examples -f example.mk
+# ./examples/dom-structure-dump examples/files/test.html
+
CC := gcc
LD := gcc
diff --git a/include/dom/core/tokenlist.h b/include/dom/core/tokenlist.h
new file mode 100644
index 0000000..718d3a5
--- /dev/null
+++ b/include/dom/core/tokenlist.h
@@ -0,0 +1,43 @@
+/*
+ * This file is part of libdom.
+ * Licensed under the MIT License,
+ * http://www.opensource.org/licenses/mit-license.php
+ * Copyright 2022 Daniel Silverstone <dsilvers@netsurf-browser.org>
+ */
+
+#ifndef dom_core_tokenlist_h_
+#define dom_core_tokenlist_h_
+
+#include <dom/core/exceptions.h>
+
+struct dom_element;
+struct dom_string;
+
+typedef struct dom_tokenlist dom_tokenlist;
+
+void dom_tokenlist_ref(struct dom_tokenlist *list);
+void dom_tokenlist_unref(struct dom_tokenlist *list);
+
+dom_exception dom_tokenlist_create(struct dom_element *ele, struct dom_string *attr, dom_tokenlist **list);
+
+dom_exception dom_tokenlist_get_length(struct dom_tokenlist *list,
+ uint32_t *length);
+dom_exception _dom_tokenlist_item(struct dom_tokenlist *list,
+ uint32_t index, struct dom_string **value);
+
+#define dom_tokenlist_item(l, i, n) _dom_tokenlist_item((dom_tokenlist *) (l), \
+ (uint32_t) (i), (struct dom_string **) (n))
+
+dom_exception dom_tokenlist_get_value(struct dom_tokenlist *list,
+ struct dom_string **value);
+
+dom_exception dom_tokenlist_set_value(struct dom_tokenlist *list,
+ struct dom_string *value);
+
+dom_exception dom_tokenlist_contains(struct dom_tokenlist *list, struct dom_string *value, bool *contains);
+
+dom_exception dom_tokenlist_add(struct dom_tokenlist *list, struct dom_string *value);
+
+dom_exception dom_tokenlist_remove(struct dom_tokenlist *list, struct dom_string *value);
+
+#endif
diff --git a/include/dom/dom.h b/include/dom/dom.h
index c10e5a6..00bf3d9 100644
--- a/include/dom/dom.h
+++ b/include/dom/dom.h
@@ -32,6 +32,7 @@
#include <dom/core/doc_fragment.h>
#include <dom/core/entity_ref.h>
#include <dom/core/nodelist.h>
+#include <dom/core/tokenlist.h>
#include <dom/core/string.h>
#include <dom/core/text.h>
#include <dom/core/pi.h>
diff --git a/include/dom/html/html_elements.h b/include/dom/html/html_elements.h
index 5b54bbe..6e954c5 100644
--- a/include/dom/html/html_elements.h
+++ b/include/dom/html/html_elements.h
@@ -12,6 +12,7 @@
DOM_HTML_ELEMENT_STRINGS_ENTRY(_UNKNOWN) \
DOM_HTML_ELEMENT_STRINGS_ENTRY(A) \
DOM_HTML_ELEMENT_STRINGS_ENTRY(ABBR) \
+ DOM_HTML_ELEMENT_STRINGS_ENTRY(ACRONYM) \
DOM_HTML_ELEMENT_STRINGS_ENTRY(ADDRESS) \
DOM_HTML_ELEMENT_STRINGS_ENTRY(APPLET) \
DOM_HTML_ELEMENT_STRINGS_ENTRY(AREA) \
@@ -23,6 +24,8 @@
DOM_HTML_ELEMENT_STRINGS_ENTRY(BASEFONT) \
DOM_HTML_ELEMENT_STRINGS_ENTRY(BDI) \
DOM_HTML_ELEMENT_STRINGS_ENTRY(BDO) \
+ DOM_HTML_ELEMENT_STRINGS_ENTRY(BGSOUND) \
+ DOM_HTML_ELEMENT_STRINGS_ENTRY(BIG) \
DOM_HTML_ELEMENT_STRINGS_ENTRY(BLOCKQUOTE) \
DOM_HTML_ELEMENT_STRINGS_ENTRY(BODY) \
DOM_HTML_ELEMENT_STRINGS_ENTRY(BR) \
@@ -81,11 +84,14 @@
DOM_HTML_ELEMENT_STRINGS_ENTRY(MAIN) \
DOM_HTML_ELEMENT_STRINGS_ENTRY(MAP) \
DOM_HTML_ELEMENT_STRINGS_ENTRY(MARK) \
+ DOM_HTML_ELEMENT_STRINGS_ENTRY(MARQUEE) \
DOM_HTML_ELEMENT_STRINGS_ENTRY(MENU) \
DOM_HTML_ELEMENT_STRINGS_ENTRY(MENUITEM) \
DOM_HTML_ELEMENT_STRINGS_ENTRY(META) \
DOM_HTML_ELEMENT_STRINGS_ENTRY(METER) \
DOM_HTML_ELEMENT_STRINGS_ENTRY(NAV) \
+ DOM_HTML_ELEMENT_STRINGS_ENTRY(NOBR) \
+ DOM_HTML_ELEMENT_STRINGS_ENTRY(NOFRAMES) \
DOM_HTML_ELEMENT_STRINGS_ENTRY(NOSCRIPT) \
DOM_HTML_ELEMENT_STRINGS_ENTRY(OBJECT) \
DOM_HTML_ELEMENT_STRINGS_ENTRY(OL) \
@@ -108,7 +114,9 @@
DOM_HTML_ELEMENT_STRINGS_ENTRY(SELECT) \
DOM_HTML_ELEMENT_STRINGS_ENTRY(SMALL) \
DOM_HTML_ELEMENT_STRINGS_ENTRY(SOURCE) \
+ DOM_HTML_ELEMENT_STRINGS_ENTRY(SPACER) \
DOM_HTML_ELEMENT_STRINGS_ENTRY(SPAN) \
+ DOM_HTML_ELEMENT_STRINGS_ENTRY(STRIKE) \
DOM_HTML_ELEMENT_STRINGS_ENTRY(STRONG) \
DOM_HTML_ELEMENT_STRINGS_ENTRY(STYLE) \
DOM_HTML_ELEMENT_STRINGS_ENTRY(SUB) \
@@ -126,6 +134,7 @@
DOM_HTML_ELEMENT_STRINGS_ENTRY(TITLE) \
DOM_HTML_ELEMENT_STRINGS_ENTRY(TR) \
DOM_HTML_ELEMENT_STRINGS_ENTRY(TRACK) \
+ DOM_HTML_ELEMENT_STRINGS_ENTRY(TT) \
DOM_HTML_ELEMENT_STRINGS_ENTRY(U) \
DOM_HTML_ELEMENT_STRINGS_ENTRY(UL) \
DOM_HTML_ELEMENT_STRINGS_ENTRY(VAR) \
diff --git a/include/dom/walk.h b/include/dom/walk.h
new file mode 100644
index 0000000..5de3546
--- /dev/null
+++ b/include/dom/walk.h
@@ -0,0 +1,65 @@
+/*
+ * This file is part of libdom.
+ * Licensed under the MIT License,
+ * http://www.opensource.org/licenses/mit-license.php
+ * Copyright 2021 Michael Drake <tlsa@netsurf-browser.org>
+ */
+
+/** \file
+ * This is an API for walking a loaded DOM.
+ */
+
+#ifndef dom_walk_h_
+#define dom_walk_h_
+
+enum dom_walk_stage {
+ DOM_WALK_STAGE_ENTER,
+ DOM_WALK_STAGE_LEAVE,
+};
+
+enum dom_walk_enable {
+ DOM_WALK_ENABLE_ENTER = (1 << DOM_WALK_STAGE_ENTER),
+ DOM_WALK_ENABLE_LEAVE = (1 << DOM_WALK_STAGE_LEAVE),
+ DOM_WALK_ENABLE_ALL = DOM_WALK_ENABLE_ENTER | DOM_WALK_ENABLE_LEAVE,
+};
+
+enum dom_walk_cmd {
+ DOM_WALK_CMD_CONTINUE, /**< Continue the tree walk. */
+ DOM_WALK_CMD_ABORT, /**< Early termination of the tree walk. */
+ DOM_WALK_CMD_SKIP, /**< Skip children (only for \ref DOM_WALK_ENABLE_ENTER). */
+};
+
+/**
+ * DOM walking callback.
+ *
+ * Client callback for DOM walk.
+ *
+ * \param[in] stage Whether the \ref node is being entered or left.
+ * \param[in] node The node being walked. Client must take ref itself.
+ * \param[in] type The node type.
+ * \param[in] pw Client private data.
+ * \return Tree walking client command.
+ */
+typedef enum dom_walk_cmd (*dom_walk_cb)(
+ enum dom_walk_stage stage,
+ dom_node_type type,
+ dom_node *node,
+ void *pw);
+
+
+/**
+ * Walk a DOM subtree.
+ *
+ * \param[in] mask Mask of stages to enable callback for.
+ * \param[in] cb The client callback function.
+ * \param[in] root Node to start walk from.
+ * \param[in] pw The client's private data.
+ * \return false for early termination of walk, true otherwise.
+ */
+dom_exception libdom_treewalk(
+ enum dom_walk_enable mask,
+ dom_walk_cb cb,
+ dom_node *root,
+ void *pw);
+
+#endif
diff --git a/src/core/Makefile b/src/core/Makefile
index 41fd51f..5d9c969 100644
--- a/src/core/Makefile
+++ b/src/core/Makefile
@@ -6,6 +6,6 @@ DIR_SOURCES := \
text.c typeinfo.c comment.c \
namednodemap.c nodelist.c \
cdatasection.c document_type.c entity_ref.c pi.c \
- doc_fragment.c document.c
+ doc_fragment.c document.c tokenlist.c
include $(NSBUILD)/Makefile.subdir
diff --git a/src/core/element.c b/src/core/element.c
index 3b031a0..05dc8c6 100644
--- a/src/core/element.c
+++ b/src/core/element.c
@@ -1232,9 +1232,11 @@ dom_exception _dom_element_has_class(struct dom_element *element,
/**
* Get a named ancestor node
*
+ * The caller is responsible for unreffing the returned node.
+ *
* \param element Element to consider
* \param name Node name to look for
- * \param ancestor Pointer to location to receive node pointer
+ * \param ancestor Pointer to location to receive node.
* \return DOM_NO_ERR.
*/
dom_exception dom_element_named_ancestor_node(dom_element *element,
@@ -1251,7 +1253,7 @@ dom_exception dom_element_named_ancestor_node(dom_element *element,
assert(node->name != NULL);
if (dom_string_caseless_lwc_isequal(node->name, name)) {
- *ancestor = (dom_element *)node;
+ *ancestor = (dom_element *)dom_node_ref(node);
break;
}
}
@@ -1262,6 +1264,8 @@ dom_exception dom_element_named_ancestor_node(dom_element *element,
/**
* Get a named parent node
*
+ * The caller is responsible for unreffing the returned node.
+ *
* \param element Element to consider
* \param name Node name to look for
* \param parent Pointer to location to receive node pointer
@@ -1281,7 +1285,7 @@ dom_exception dom_element_named_parent_node(dom_element *element,
assert(node->name != NULL);
if (dom_string_caseless_lwc_isequal(node->name, name)) {
- *parent = (dom_element *)node;
+ *parent = (dom_element *)dom_node_ref(node);
}
break;
}
@@ -1292,6 +1296,8 @@ dom_exception dom_element_named_parent_node(dom_element *element,
/**
* Get a named parent node
*
+ * The caller is responsible for unreffing the returned node.
+ *
* \param element Element to consider
* \param name Node name to look for
* \param parent Pointer to location to receive node pointer
@@ -1308,7 +1314,7 @@ dom_exception dom_element_parent_node(dom_element *element,
if (node->type != DOM_ELEMENT_NODE)
continue;
- *parent = (dom_element *)node;
+ *parent = (dom_element *)dom_node_ref(node);
break;
}
diff --git a/src/core/tokenlist.c b/src/core/tokenlist.c
new file mode 100644
index 0000000..cc93a8c
--- /dev/null
+++ b/src/core/tokenlist.c
@@ -0,0 +1,547 @@
+/*
+ * This file is part of libdom.
+ * Licensed under the MIT License,
+ * http://www.opensource.org/licenses/mit-license.php
+ * Copyright 2022 Daniel Silverstone <dsilvers@digital-scurf.org>
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <dom/core/element.h>
+#include <dom/core/nodelist.h>
+#include <dom/core/tokenlist.h>
+#include <dom/core/string.h>
+#include <dom/events/event.h>
+#include <dom/events/event_target.h>
+#include <dom/events/event_listener.h>
+#include <dom/events/mutation_event.h>
+
+#include "core/element.h"
+#include "core/document.h"
+
+#include "utils/utils.h"
+
+#define DOM_TOKENLIST_GROW_INCREMENT 4
+
+struct dom_tokenlist {
+ uint32_t refcnt;
+ dom_element *ele;
+ dom_string *attr;
+ dom_event_listener *listener;
+ dom_string *last_set;
+ bool needs_parse;
+ /* Parsed content, for optimal access */
+ dom_string **entries;
+ uint32_t len;
+ uint32_t alloc;
+};
+
+/* Handle a DOMAttrModified event which might be to do with our attribute */
+
+static void _dom_tokenlist_handle_attrmodified(dom_event *evt, void *pw)
+{
+ dom_mutation_event *mutevt = (dom_mutation_event *)evt;
+ dom_tokenlist *list = (dom_tokenlist *)pw;
+ dom_exception exc;
+ dom_string *value;
+
+ {
+ dom_event_target *target;
+ exc = dom_event_get_target(evt, &target);
+ if (exc != DOM_NO_ERR)
+ return;
+ dom_node_unref(target);
+ if (target != (dom_event_target *)list->ele)
+ return;
+ }
+
+ {
+ dom_string *attr;
+ exc = dom_mutation_event_get_attr_name(mutevt, &attr);
+ if (exc != DOM_NO_ERR)
+ return;
+ if (!dom_string_isequal(attr, list->attr)) {
+ dom_string_unref(attr);
+ return;
+ }
+ dom_string_unref(attr);
+ }
+
+ /* At this point we know that this is a mutation of our attribute on our
+ * node */
+
+ exc = dom_mutation_event_get_new_value(mutevt, &value);
+ if (exc != DOM_NO_ERR)
+ return;
+
+ if (list->last_set != NULL &&
+ dom_string_isequal(list->last_set, value)) {
+ /* We've just seen the mutation event for one of our own set
+ * operations */
+ dom_string_unref(value);
+ return;
+ }
+
+ /* Mark that we need to re-parse the tokenlist on the next request */
+ list->needs_parse = true;
+
+ dom_string_unref(value);
+}
+
+static dom_exception _dom_tokenlist_make_room(dom_tokenlist *list)
+{
+ if (list->len == list->alloc) {
+ uint32_t new_alloc = list->alloc + DOM_TOKENLIST_GROW_INCREMENT;
+ dom_string **new_entries = realloc(
+ list->entries, new_alloc * sizeof(dom_string *));
+ if (new_entries == NULL)
+ return DOM_NO_MEM_ERR;
+ list->alloc = new_alloc;
+ list->entries = new_entries;
+ }
+
+ return DOM_NO_ERR;
+}
+
+static dom_exception _dom_tokenlist_reparse(dom_tokenlist *list)
+{
+ dom_exception exc;
+ dom_string *value;
+ const char *pos;
+ uint32_t remaining, check;
+ uint32_t n_entries = 0;
+ dom_string *temp;
+ bool found;
+
+ if (!list->needs_parse)
+ return DOM_NO_ERR;
+
+ /* Clean down the current entries */
+ while (list->len-- > 0)
+ dom_string_unref(list->entries[list->len]);
+ list->len = 0;
+
+ /* Get the "new" attribute value */
+ exc = dom_element_get_attribute(list->ele, list->attr, &value);
+ if (exc != DOM_NO_ERR)
+ return exc;
+
+ /* If there is no value, we're an empty list and we're done */
+ if (value == NULL) {
+ list->needs_parse = false;
+ return DOM_NO_ERR;
+ }
+
+ /* OK, there's something here to do, so let's do it... */
+
+ /* Count number of entries */
+ for (pos = dom_string_data(value), remaining = dom_string_length(value);
+ remaining > 0;) {
+ if (*pos != ' ') {
+ while (*pos != ' ' && remaining > 0) {
+ remaining--;
+ pos++;
+ }
+ n_entries++;
+ } else {
+ while (*pos == ' ' && remaining > 0) {
+ remaining--;
+ pos++;
+ }
+ }
+ }
+
+ /* If there are no entries (all whitespace) just bail here */
+ if (n_entries == 0) {
+ list->needs_parse = false;
+ dom_string_unref(value);
+ return DOM_NO_ERR;
+ }
+
+ /* If we need more room, reallocate the buffer */
+ if (list->alloc < n_entries) {
+ dom_string **new_alloc = realloc(
+ list->entries, n_entries * sizeof(dom_string *));
+ if (new_alloc == NULL) {
+ dom_string_unref(value);
+ return DOM_NO_MEM_ERR;
+ }
+ list->entries = new_alloc;
+ list->alloc = n_entries;
+ }
+
+ /* And now parse those entries into the buffer */
+ for (pos = dom_string_data(value),
+ remaining = dom_string_length(value),
+ n_entries = 0;
+ remaining > 0;) {
+ if (*pos != ' ') {
+ const char *s = pos;
+ while (*pos != ' ' && remaining > 0) {
+ pos++;
+ remaining--;
+ }
+ exc = dom_string_create_interned((const uint8_t *)s,
+ pos - s,
+ &temp);
+ if (exc != DOM_NO_ERR) {
+ dom_string_unref(value);
+ return exc;
+ }
+ found = false;
+ for (check = 0; check < list->len; check++) {
+ if (dom_string_isequal(temp,
+ list->entries[check])) {
+ found = true;
+ break;
+ }
+ }
+ if (found == true) {
+ dom_string_unref(temp);
+ } else {
+ list->entries[list->len] = temp;
+ list->len++;
+ }
+ } else {
+ while (*pos == ' ' && remaining > 0) {
+ pos++;
+ remaining--;
+ }
+ }
+ }
+
+ dom_string_unref(value);
+ list->needs_parse = false;
+
+ return DOM_NO_ERR;
+}
+
+static dom_exception _dom_tokenlist_reify(dom_tokenlist *list)
+{
+ dom_exception exc;
+ uint32_t nchars = 0, n;
+ char *buffer, *next;
+ dom_string *output;
+
+ if (list->len == 0) {
+ if (list->last_set != NULL) {
+ dom_string_unref(list->last_set);
+ }
+ list->last_set = dom_string_ref(
+ list->ele->base.owner->_memo_empty);
+ return dom_element_set_attribute(list->ele,
+ list->attr,
+ list->last_set);
+ }
+
+ for (n = 0; n < list->len; ++n)
+ nchars += dom_string_length(list->entries[n]);
+
+ buffer = calloc(1, nchars + list->len);
+ if (buffer == NULL)
+ return DOM_NO_MEM_ERR;
+
+ for (next = buffer, n = 0; n < list->len; ++n) {
+ uint32_t slen = dom_string_length(list->entries[n]);
+ memcpy(next, dom_string_data(list->entries[n]), slen);
+ next[slen] = ' ';
+ next += slen + 1;
+ }
+
+ exc = dom_string_create_interned((const uint8_t *)buffer,
+ nchars + list->len - 1,
+ &output);
+ free(buffer);
+ if (exc != DOM_NO_ERR)
+ return exc;
+
+ if (list->last_set != NULL) {
+ dom_string_unref(list->last_set);
+ }
+ list->last_set = output;
+
+ return dom_element_set_attribute(list->ele, list->attr, list->last_set);
+}
+
+/**********************************************************************************/
+
+/**
+ * Create a tokenlist
+ *
+ * \param ele The element which owns the tokenlist attribute
+ * \param attr The name of the attribute we are treating as a tokenlist
+ * \param list The tokenlist output which is set on success
+ * \return DOM_NO_ERR on success, DOM_NO_MEM_ERR on memory exhaustion
+ *
+ * The returned list will already be referenced, so the client need not
+ * do so explicitly. The client must unref the list once finished with it.
+ *
+ * This list will take its own references to ::ele and ::attr
+ */
+dom_exception
+dom_tokenlist_create(dom_element *ele, dom_string *attr, dom_tokenlist **list)
+{
+ dom_tokenlist *l;
+ dom_exception exc;
+
+ l = calloc(1, sizeof(dom_tokenlist));
+ if (l == NULL)
+ return DOM_NO_MEM_ERR;
+
+ l->refcnt = 1;
+ l->ele = (dom_element *)dom_node_ref(ele);
+ l->attr = dom_string_ref(attr);
+ l->needs_parse = true;
+
+ exc = dom_event_listener_create(_dom_tokenlist_handle_attrmodified,
+ l,
+ &l->listener);
+ if (exc != DOM_NO_ERR)
+ goto fail;
+
+ exc = dom_event_target_add_event_listener(
+ ele,
+ ele->base.owner->_memo_domattrmodified,
+ l->listener,
+ false);
+
+ if (exc != DOM_NO_ERR)
+ goto fail;
+
+ *list = l;
+
+ return DOM_NO_ERR;
+
+fail:
+ if (l->listener != NULL)
+ dom_event_listener_unref(l->listener);
+ dom_node_unref(l->ele);
+ dom_string_unref(l->attr);
+ free(l);
+ return exc;
+}
+
+/**
+ * Claim a ref on a tokenlist
+ *
+ * \param list The tokenlist to claim a ref on
+ */
+void dom_tokenlist_ref(dom_tokenlist *list)
+{
+ assert(list != NULL);
+ list->refcnt++;
+}
+
+/**
+ * Release a ref on a tokenlist
+ *
+ * \param list The list to release the reference of
+ *
+ * If you release the last ref, this cleans up the tokenlist
+ */
+void dom_tokenlist_unref(dom_tokenlist *list)
+{
+ assert(list != NULL);
+
+ if (--list->refcnt > 0)
+ return;
+
+ if (list->alloc > 0) {
+ while (list->len-- > 0)
+ dom_string_unref(list->entries[list->len]);
+ free(list->entries);
+ }
+
+ dom_event_target_remove_event_listener(
+ list->ele,
+ list->ele->base.owner->_memo_domattrmodified,
+ list->listener,
+ false);
+
+ dom_event_listener_unref(list->listener);
+
+ if (list->last_set != NULL)
+ dom_string_unref(list->last_set);
+
+ dom_string_unref(list->attr);
+ dom_node_unref(list->ele);
+
+ free(list);
+}
+
+/**
+ * Get the length of the tokenlist
+ *
+ * \param list The list to get the length of
+ * \param length Length of the list outputs here
+ * \return DOM_NO_ERR on success, otherwise the failure code
+ */
+dom_exception dom_tokenlist_get_length(dom_tokenlist *list, uint32_t *length)
+{
+ dom_exception exc;
+ assert(list != NULL);
+
+ exc = _dom_tokenlist_reparse(list);
+ if (exc != DOM_NO_ERR)
+ return exc;
+
+ *length = list->len;
+
+ return DOM_NO_ERR;
+}
+
+/**
+ * Get a particular item from the tokenlist
+ *
+ * \param list The list to retrieve the item from
+ * \param index The index of the item to retrieve
+ * \param value The value of the item returns here
+ * \return DOM_NO_ERR on success, otherwise the failure code
+ */
+dom_exception
+_dom_tokenlist_item(dom_tokenlist *list, uint32_t index, dom_string **value)
+{
+ dom_exception exc;
+ assert(list != NULL);
+
+ exc = _dom_tokenlist_reparse(list);
+ if (exc != DOM_NO_ERR)
+ return exc;
+
+ if (index >= list->len) {
+ *value = NULL;
+ return DOM_NO_ERR;
+ }
+
+ *value = dom_string_ref(list->entries[index]);
+ return DOM_NO_ERR;
+}
+
+/**
+ * Retrieve the value of the tokenlist as a string
+ *
+ * \param list The list to retrieve the value of
+ * \param value The value of the list returns here
+ * \return DOM_NO_ERR on success, otherwise the failure code
+ */
+dom_exception dom_tokenlist_get_value(dom_tokenlist *list, dom_string **value)
+{
+ assert(list != NULL);
+
+ return dom_element_get_attribute(list->ele, list->attr, value);
+}
+
+/**
+ * Set the value of the tokenlist as a string
+ *
+ * \param list The list to set the value of
+ * \param value The value to set
+ * \return DOM_NO_ERR on success, otherwise the failure code
+ *
+ */
+dom_exception dom_tokenlist_set_value(dom_tokenlist *list, dom_string *value)
+{
+ assert(list != NULL);
+
+ return dom_element_set_attribute(list->ele, list->attr, value);
+}
+
+/**
+ * Check if the given value is in the tokenlist
+ *
+ * \param list The list to scan for the given value
+ * \param value The value to look for in the token list
+ * \param contains This will be set based on whether or not the value is present
+ * \return DOM_NO_ERR on success, otherwise the failure code
+ */
+dom_exception
+dom_tokenlist_contains(dom_tokenlist *list, dom_string *value, bool *contains)
+{
+ dom_exception exc;
+ uint32_t n;
+
+ assert(list != NULL);
+
+ exc = _dom_tokenlist_reparse(list);
+ if (exc != DOM_NO_ERR)
+ return exc;
+
+ *contains = false;
+
+ for (n = 0; n < list->len; n++) {
+ if (dom_string_isequal(value, list->entries[n])) {
+ *contains = true;
+ break;
+ }
+ }
+
+ return DOM_NO_ERR;
+}
+
+/**
+ * Add the given value to the tokenlist
+ *
+ * \param list The list to add to
+ * \param value The value to add
+ * \return DOM_NO_ERR on success, otherwise the failure code
+ */
+dom_exception dom_tokenlist_add(dom_tokenlist *list, dom_string *value)
+{
+ dom_exception exc;
+ bool present = false;
+
+ assert(list != NULL);
+
+ exc = dom_tokenlist_contains(list, value, &present);
+ if (exc != DOM_NO_ERR)
+ return exc;
+
+ if (present == true)
+ return DOM_NO_ERR;
+
+ exc = _dom_tokenlist_make_room(list);
+ if (exc != DOM_NO_ERR)
+ return exc;
+
+ list->entries[list->len++] = dom_string_ref(value);
+
+ exc = _dom_tokenlist_reify(list);
+
+ return exc;
+}
+
+/**
+ * Remove the given value from the tokenlist
+ *
+ * \param list The list to remove from
+ * \param value The value to remove
+ * \return DOM_NO_ERR on success, otherwise the failure code
+ */
+dom_exception dom_tokenlist_remove(dom_tokenlist *list, dom_string *value)
+{
+ dom_exception exc;
+ uint32_t n, m;
+
+ assert(list != NULL);
+
+ exc = _dom_tokenlist_reparse(list);
+ if (exc != DOM_NO_ERR)
+ return false;
+
+ for (n = 0; n < list->len; ++n) {
+ if (dom_string_isequal(value, list->entries[n])) {
+ dom_string_unref(list->entries[n]);
+ for (m = n + 1; m < list->len; ++m) {
+ list->entries[m - 1] = list->entries[m];
+ }
+ list->len--;
+ break;
+ }
+ }
+
+ exc = _dom_tokenlist_reify(list);
+
+ return exc;
+}
diff --git a/src/core/tokenlist.h b/src/core/tokenlist.h
new file mode 100644
index 0000000..325d726
--- /dev/null
+++ b/src/core/tokenlist.h
@@ -0,0 +1,18 @@
+/*
+ * This file is part of libdom.
+ * Licensed under the MIT License,
+ * http://www.opensource.org/licenses/mit-license.php
+ * Copyright 2007 John-Mark Bell <jmb@netsurf-browser.org>
+ */
+
+#ifndef dom_internal_core_tokenlist_h_
+#define dom_internal_core_tokenlist_h_
+
+#include <stdbool.h>
+
+#include <dom/core/tokenlist.h>
+
+struct dom_element;
+struct dom_string;
+
+#endif
diff --git a/src/html/html_document.c b/src/html/html_document.c
index 5471f4f..cf3c25d 100644
--- a/src/html/html_document.c
+++ b/src/html/html_document.c
@@ -319,6 +319,8 @@ static inline dom_html_element_type _dom_html_document_get_element_type(
RETURN_IF_MATCH(upper, DOM_HTML_ELEMENT_TYPE_RP)
else
RETURN_IF_MATCH(upper, DOM_HTML_ELEMENT_TYPE_RT)
+ else
+ RETURN_IF_MATCH(upper, DOM_HTML_ELEMENT_TYPE_TT)
break;
case 3:
RETURN_IF_MATCH(upper, DOM_HTML_ELEMENT_TYPE_DIV)
@@ -354,6 +356,8 @@ static inline dom_html_element_type _dom_html_document_get_element_type(
RETURN_IF_MATCH(upper, DOM_HTML_ELEMENT_TYPE_BDO)
else
RETURN_IF_MATCH(upper, DOM_HTML_ELEMENT_TYPE_WBR)
+ else
+ RETURN_IF_MATCH(upper, DOM_HTML_ELEMENT_TYPE_BIG)
break;
case 4:
RETURN_IF_MATCH(upper, DOM_HTML_ELEMENT_TYPE_META)
@@ -395,6 +399,8 @@ static inline dom_html_element_type _dom_html_document_get_element_type(
RETURN_IF_MATCH(upper, DOM_HTML_ELEMENT_TYPE_BASE)
else
RETURN_IF_MATCH(upper, DOM_HTML_ELEMENT_TYPE_TIME)
+ else
+ RETURN_IF_MATCH(upper, DOM_HTML_ELEMENT_TYPE_NOBR)
break;
case 5:
RETURN_IF_MATCH(upper, DOM_HTML_ELEMENT_TYPE_INPUT)
@@ -469,6 +475,10 @@ static inline dom_html_element_type _dom_html_document_get_element_type(
RETURN_IF_MATCH(upper, DOM_HTML_ELEMENT_TYPE_SOURCE)
else
RETURN_IF_MATCH(upper, DOM_HTML_ELEMENT_TYPE_STRONG)
+ else
+ RETURN_IF_MATCH(upper, DOM_HTML_ELEMENT_TYPE_SPACER)
+ else
+ RETURN_IF_MATCH(upper, DOM_HTML_ELEMENT_TYPE_STRIKE)
break;
case 7:
RETURN_IF_MATCH(upper, DOM_HTML_ELEMENT_TYPE_CAPTION)
@@ -486,6 +496,12 @@ static inline dom_html_element_type _dom_html_document_get_element_type(
RETURN_IF_MATCH(upper, DOM_HTML_ELEMENT_TYPE_SECTION)
else
RETURN_IF_MATCH(upper, DOM_HTML_ELEMENT_TYPE_SUMMARY)
+ else
+ RETURN_IF_MATCH(upper, DOM_HTML_ELEMENT_TYPE_ACRONYM)
+ else
+ RETURN_IF_MATCH(upper, DOM_HTML_ELEMENT_TYPE_BGSOUND)
+ else
+ RETURN_IF_MATCH(upper, DOM_HTML_ELEMENT_TYPE_MARQUEE)
break;
case 8:
RETURN_IF_MATCH(upper, DOM_HTML_ELEMENT_TYPE_TEXTAREA)
@@ -509,6 +525,8 @@ static inline dom_html_element_type _dom_html_document_get_element_type(
RETURN_IF_MATCH(upper, DOM_HTML_ELEMENT_TYPE_PROGRESS)
else
RETURN_IF_MATCH(upper, DOM_HTML_ELEMENT_TYPE_TEMPLATE)
+ else
+ RETURN_IF_MATCH(upper, DOM_HTML_ELEMENT_TYPE_NOFRAMES)
break;
case 10:
RETURN_IF_MATCH(upper, DOM_HTML_ELEMENT_TYPE_BLOCKQUOTE)
@@ -809,8 +827,10 @@ _dom_html_document_create_element_internal(
case DOM_HTML_ELEMENT_TYPE_EM:
case DOM_HTML_ELEMENT_TYPE_RP:
case DOM_HTML_ELEMENT_TYPE_RT:
+ case DOM_HTML_ELEMENT_TYPE_TT:
case DOM_HTML_ELEMENT_TYPE_BDI:
case DOM_HTML_ELEMENT_TYPE_BDO:
+ case DOM_HTML_ELEMENT_TYPE_BIG:
case DOM_HTML_ELEMENT_TYPE_DFN:
case DOM_HTML_ELEMENT_TYPE_KBD:
case DOM_HTML_ELEMENT_TYPE_NAV:
@@ -823,6 +843,7 @@ _dom_html_document_create_element_internal(
case DOM_HTML_ELEMENT_TYPE_CODE:
case DOM_HTML_ELEMENT_TYPE_MAIN:
case DOM_HTML_ELEMENT_TYPE_MARK:
+ case DOM_HTML_ELEMENT_TYPE_NOBR:
case DOM_HTML_ELEMENT_TYPE_RUBY:
case DOM_HTML_ELEMENT_TYPE_SAMP:
case DOM_HTML_ELEMENT_TYPE_ASIDE:
@@ -833,10 +854,16 @@ _dom_html_document_create_element_internal(
case DOM_HTML_ELEMENT_TYPE_HEADER:
case DOM_HTML_ELEMENT_TYPE_HGROUP:
case DOM_HTML_ELEMENT_TYPE_STRONG:
+ case DOM_HTML_ELEMENT_TYPE_SPACER:
+ case DOM_HTML_ELEMENT_TYPE_STRIKE:
+ case DOM_HTML_ELEMENT_TYPE_ACRONYM:
case DOM_HTML_ELEMENT_TYPE_ADDRESS:
case DOM_HTML_ELEMENT_TYPE_ARTICLE:
+ case DOM_HTML_ELEMENT_TYPE_BGSOUND:
+ case DOM_HTML_ELEMENT_TYPE_MARQUEE:
case DOM_HTML_ELEMENT_TYPE_SECTION:
case DOM_HTML_ELEMENT_TYPE_SUMMARY:
+ case DOM_HTML_ELEMENT_TYPE_NOFRAMES:
case DOM_HTML_ELEMENT_TYPE_NOSCRIPT:
case DOM_HTML_ELEMENT_TYPE_FIGCAPTION:
/* These have no specialisation: use HTMLElement */
diff --git a/src/html/html_element.c b/src/html/html_element.c
index df0fa6a..c110455 100644
--- a/src/html/html_element.c
+++ b/src/html/html_element.c
@@ -477,7 +477,7 @@ dom_exception dom_html_element_get_int32_t_property(dom_html_element *ele,
char *s3 = _strndup(dom_string_data(s2),
dom_string_byte_length(s2));
if (s3 != NULL) {
- *value = strtoul(s3, NULL, 0);
+ *value = strtol(s3, NULL, 0);
free(s3);
} else {
err = DOM_NO_MEM_ERR;
@@ -518,7 +518,7 @@ dom_exception dom_html_element_set_int32_t_property(dom_html_element *ele,
if (err != DOM_NO_ERR)
goto fail;
- if (snprintf(numbuffer, 32, "%u", value) == 32)
+ if (snprintf(numbuffer, 32, "%"PRIu32, value) == 32)
numbuffer[31] = '\0';
err = dom_string_create((const uint8_t *) numbuffer,
@@ -607,7 +607,7 @@ dom_exception dom_html_element_set_dom_ulong_property(dom_html_element *ele,
if (err != DOM_NO_ERR)
goto fail;
- if (snprintf(numbuffer, 32, "%u", value) == 32)
+ if (snprintf(numbuffer, 32, "%"PRIu32, value) == 32)
numbuffer[31] = '\0';
err = dom_string_create((const uint8_t *) numbuffer,
diff --git a/src/utils/Makefile b/src/utils/Makefile
index 4bb586f..f891b6e 100644
--- a/src/utils/Makefile
+++ b/src/utils/Makefile
@@ -1,4 +1,4 @@
# Sources
-DIR_SOURCES := namespace.c hashtable.c character_valid.c validate.c
+DIR_SOURCES := namespace.c hashtable.c character_valid.c validate.c walk.c
include $(NSBUILD)/Makefile.subdir
diff --git a/src/utils/walk.c b/src/utils/walk.c
new file mode 100644
index 0000000..f103908
--- /dev/null
+++ b/src/utils/walk.c
@@ -0,0 +1,130 @@
+/*
+ * This file is part of libdom.
+ * Licensed under the MIT License,
+ * http://www.opensource.org/licenses/mit-license.php
+ * Copyright 2021 Michael Drake <tlsa@netsurf-browser.org>
+ */
+
+/** \file
+ * This is an API for walking a loaded DOM.
+ */
+
+#include <dom/dom.h>
+#include <dom/walk.h>
+
+/**
+ * Wrapper for calling client callback.
+ *
+ * \param[in] mask Mask of stages to enable callback for.
+ * \param[in] stage Whether the \ref node is being entered or left.
+ * \param[in] node The node being walked.
+ * \param[in] cb The client callback function.
+ * \param[in] pw The client's private data.
+ * \param[out] cmd_out Walk instruction from client.
+ * \return false for early termination of walk, true otherwise.
+ */
+static inline dom_exception dom_walk__cb(
+ enum dom_walk_enable mask,
+ enum dom_walk_stage stage,
+ dom_node *node,
+ dom_walk_cb cb,
+ void *pw,
+ enum dom_walk_cmd *cmd_out)
+{
+ if ((1 << stage) & mask) {
+ dom_node_type type;
+ dom_exception exc;
+
+ exc = dom_node_get_node_type(node, &type);
+ if (exc != DOM_NO_ERR) {
+ return exc;
+ }
+
+ *cmd_out = cb(stage, type, node, pw);
+ }
+
+ return DOM_NO_ERR;
+}
+
+/* exported interface documented in include/dom/walk.h */
+dom_exception libdom_treewalk(
+ enum dom_walk_enable mask,
+ dom_walk_cb cb,
+ dom_node *root,
+ void *pw)
+{
+ dom_node *node;
+ dom_exception exc;
+ enum dom_walk_cmd cmd = DOM_WALK_CMD_CONTINUE;
+
+ node = dom_node_ref(root);
+
+ while (cmd != DOM_WALK_CMD_ABORT) {
+ dom_node *next = NULL;
+
+ if (cmd != DOM_WALK_CMD_SKIP) {
+ exc = dom_node_get_first_child(node, &next);
+ if (exc != DOM_NO_ERR) {
+ dom_node_unref(node);
+ break;
+ }
+ }
+
+ if (next != NULL) {
+ dom_node_unref(node);
+ node = next;
+ } else {
+ /* No children; siblings & ancestor's siblings */
+ while (node != root) {
+ exc = dom_walk__cb(mask, DOM_WALK_STAGE_LEAVE,
+ node, cb, pw, &cmd);
+ if (exc != DOM_NO_ERR ||
+ cmd == DOM_WALK_CMD_ABORT) {
+ dom_node_unref(node);
+ return exc;
+ }
+
+ exc = dom_node_get_next_sibling(node, &next);
+ if (exc != DOM_NO_ERR) {
+ dom_node_unref(node);
+ node = NULL;
+ break;
+ }
+
+ if (next != NULL) {
+ /* Found next sibling. */
+ break;
+ }
+
+ exc = dom_node_get_parent_node(node, &next);
+ if (exc != DOM_NO_ERR) {
+ dom_node_unref(node);
+ return exc;
+ }
+
+ dom_node_unref(node);
+ node = next;
+ }
+
+ if (node == root) {
+ break;
+ }
+
+ dom_node_unref(node);
+ node = next;
+ }
+
+ assert(node != NULL);
+ assert(node != root);
+
+ exc = dom_walk__cb(mask, DOM_WALK_STAGE_ENTER, node,
+ cb, pw, &cmd);
+ if (exc != DOM_NO_ERR) {
+ return exc;
+ }
+ }
+
+ dom_node_unref(node);
+
+ return DOM_NO_ERR;
+}
diff --git a/test/testutils/utils.c b/test/testutils/utils.c
index c876613..379b0ad 100644
--- a/test/testutils/utils.c
+++ b/test/testutils/utils.c
@@ -27,7 +27,7 @@ void mymsg(uint32_t severity, void *ctx, const char *msg, ...)
va_start(l, msg);
- fprintf(stderr, "%d: ", severity);
+ fprintf(stderr, "%"PRIu32": ", severity);
vfprintf(stderr, msg, l);
fprintf(stderr, "\n");
}