summaryrefslogtreecommitdiff
path: root/src/utils/walk.c
blob: 20314f343d0589cef7711e3310e3d3ea0bd1c5ef (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
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]  ctx      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 *ctx,
		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, ctx);
	}

	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 *ctx)
{
	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, ctx, &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, ctx, &cmd);
		if (exc != DOM_NO_ERR) {
			return exc;
		}
	}

	dom_node_unref(node);

	return DOM_NO_ERR;
}