Subversion Repositories Kolibri OS

Rev

Rev 4364 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
3584 sourcerer 1
/*
2
 * Copyright 2009 John-Mark Bell 
3
 *
4
 * This file is part of NetSurf, http://www.netsurf-browser.org/
5
 *
6
 * NetSurf is free software; you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation; version 2 of the License.
9
 *
10
 * NetSurf is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License
16
 * along with this program.  If not, see .
17
 */
18
 
19
#include 
20
#include 
21
#include 
22
#include 
23
 
24
#include "content/content_protected.h"
25
#include "content/urldb.h"
26
#include "css/internal.h"
27
#include "css/select.h"
28
#include "css/utils.h"
29
#include "desktop/gui.h"
30
#include "desktop/options.h"
31
#include "utils/corestrings.h"
32
#include "utils/log.h"
33
#include "utils/url.h"
34
#include "utils/utils.h"
35
 
36
static css_error node_name(void *pw, void *node, css_qname *qname);
37
static css_error node_classes(void *pw, void *node,
38
		lwc_string ***classes, uint32_t *n_classes);
39
static css_error node_id(void *pw, void *node, lwc_string **id);
40
static css_error named_ancestor_node(void *pw, void *node,
41
		const css_qname *qname, void **ancestor);
42
static css_error named_parent_node(void *pw, void *node,
43
		const css_qname *qname, void **parent);
44
static css_error named_sibling_node(void *pw, void *node,
45
		const css_qname *qname, void **sibling);
46
static css_error named_generic_sibling_node(void *pw, void *node,
47
		const css_qname *qname, void **sibling);
48
static css_error parent_node(void *pw, void *node, void **parent);
49
static css_error sibling_node(void *pw, void *node, void **sibling);
50
static css_error node_has_name(void *pw, void *node,
51
		const css_qname *qname, bool *match);
52
static css_error node_has_class(void *pw, void *node,
53
		lwc_string *name, bool *match);
54
static css_error node_has_id(void *pw, void *node,
55
		lwc_string *name, bool *match);
56
static css_error node_has_attribute(void *pw, void *node,
57
		const css_qname *qname, bool *match);
58
static css_error node_has_attribute_equal(void *pw, void *node,
59
		const css_qname *qname, lwc_string *value,
60
		bool *match);
61
static css_error node_has_attribute_dashmatch(void *pw, void *node,
62
		const css_qname *qname, lwc_string *value,
63
		bool *match);
64
static css_error node_has_attribute_includes(void *pw, void *node,
65
		const css_qname *qname, lwc_string *value,
66
		bool *match);
67
static css_error node_has_attribute_prefix(void *pw, void *node,
68
		const css_qname *qname, lwc_string *value,
69
		bool *match);
70
static css_error node_has_attribute_suffix(void *pw, void *node,
71
		const css_qname *qname, lwc_string *value,
72
		bool *match);
73
static css_error node_has_attribute_substring(void *pw, void *node,
74
		const css_qname *qname, lwc_string *value,
75
		bool *match);
76
static css_error node_is_root(void *pw, void *node, bool *match);
77
static css_error node_count_siblings(void *pw, void *node,
78
		bool same_name, bool after, int32_t *count);
79
static css_error node_is_empty(void *pw, void *node, bool *match);
80
static css_error node_is_link(void *pw, void *node, bool *match);
81
static css_error node_is_visited(void *pw, void *node, bool *match);
82
static css_error node_is_hover(void *pw, void *node, bool *match);
83
static css_error node_is_active(void *pw, void *node, bool *match);
84
static css_error node_is_focus(void *pw, void *node, bool *match);
85
static css_error node_is_enabled(void *pw, void *node, bool *match);
86
static css_error node_is_disabled(void *pw, void *node, bool *match);
87
static css_error node_is_checked(void *pw, void *node, bool *match);
88
static css_error node_is_target(void *pw, void *node, bool *match);
89
static css_error node_is_lang(void *pw, void *node,
90
		lwc_string *lang, bool *match);
91
static css_error node_presentational_hint(void *pw, void *node,
92
		uint32_t property, css_hint *hint);
93
static css_error ua_default_for_property(void *pw, uint32_t property,
94
		css_hint *hint);
95
 
96
static int cmp_colour_name(const void *a, const void *b);
97
static bool parse_named_colour(const char *data, css_color *result);
98
static bool parse_dimension(const char *data, bool strict,
99
		css_fixed *length, css_unit *unit);
100
static bool parse_number(const char *data, bool non_negative, bool real,
101
		css_fixed *value, size_t *consumed);
102
static bool parse_font_size(const char *size, uint8_t *val,
103
		css_fixed *len, css_unit *unit);
104
 
105
static css_computed_style *nscss_get_initial_style(nscss_select_ctx *ctx,
106
		css_allocator_fn, void *pw);
107
 
108
static bool isWhitespace(char c);
109
static bool isHex(char c);
110
static uint8_t charToHex(char c);
111
 
112
/**
113
 * Selection callback table for libcss
114
 */
115
static css_select_handler selection_handler = {
116
	CSS_SELECT_HANDLER_VERSION_1,
117
 
118
	node_name,
119
	node_classes,
120
	node_id,
121
	named_ancestor_node,
122
	named_parent_node,
123
	named_sibling_node,
124
	named_generic_sibling_node,
125
	parent_node,
126
	sibling_node,
127
	node_has_name,
128
	node_has_class,
129
	node_has_id,
130
	node_has_attribute,
131
	node_has_attribute_equal,
132
	node_has_attribute_dashmatch,
133
	node_has_attribute_includes,
134
	node_has_attribute_prefix,
135
	node_has_attribute_suffix,
136
	node_has_attribute_substring,
137
	node_is_root,
138
	node_count_siblings,
139
	node_is_empty,
140
	node_is_link,
141
	node_is_visited,
142
	node_is_hover,
143
	node_is_active,
144
	node_is_focus,
145
	node_is_enabled,
146
	node_is_disabled,
147
	node_is_checked,
148
	node_is_target,
149
	node_is_lang,
150
	node_presentational_hint,
151
	ua_default_for_property,
152
	nscss_compute_font_size
153
};
154
 
155
/**
156
 * Create an inline style
157
 *
158
 * \param data          Source data
159
 * \param len           Length of data in bytes
160
 * \param charset       Charset of data, or NULL if unknown
161
 * \param url           URL of document containing data
162
 * \param allow_quirks  True to permit CSS parsing quirks
163
 * \param alloc         Memory allocation function
164
 * \param pw            Private word for allocator
165
 * \return Pointer to stylesheet, or NULL on failure.
166
 */
167
css_stylesheet *nscss_create_inline_style(const uint8_t *data, size_t len,
168
		const char *charset, const char *url, bool allow_quirks,
169
		css_allocator_fn alloc, void *pw)
170
{
171
	css_stylesheet_params params;
172
	css_stylesheet *sheet;
173
	css_error error;
174
 
175
	params.params_version = CSS_STYLESHEET_PARAMS_VERSION_1;
176
	params.level = CSS_LEVEL_DEFAULT;
177
	params.charset = charset;
178
	params.url = url;
179
	params.title = NULL;
180
	params.allow_quirks = allow_quirks;
181
	params.inline_style = true;
182
	params.resolve = nscss_resolve_url;
183
	params.resolve_pw = NULL;
184
	params.import = NULL;
185
	params.import_pw = NULL;
186
	params.color = gui_system_colour;
187
	params.color_pw = NULL;
188
	params.font = NULL;
189
	params.font_pw = NULL;
190
 
191
	error = css_stylesheet_create(¶ms, alloc, pw, &sheet);
192
	if (error != CSS_OK) {
193
		LOG(("Failed creating sheet: %d", error));
194
		return NULL;
195
	}
196
 
197
	error = css_stylesheet_append_data(sheet, data, len);
198
	if (error != CSS_OK && error != CSS_NEEDDATA) {
199
		LOG(("failed appending data: %d", error));
200
		css_stylesheet_destroy(sheet);
201
		return NULL;
202
	}
203
 
204
	error = css_stylesheet_data_done(sheet);
205
	if (error != CSS_OK) {
206
		LOG(("failed completing parse: %d", error));
207
		css_stylesheet_destroy(sheet);
208
		return NULL;
209
	}
210
 
211
	return sheet;
212
}
213
 
214
/**
215
 * Get a style selection results (partial computed styles) for an element
216
 *
217
 * \param ctx             CSS selection context
218
 * \param n               Element to select for
219
 * \param media           Permitted media types
220
 * \param inline_style    Inline style associated with element, or NULL
221
 * \param alloc           Memory allocation function
222
 * \param pw              Private word for allocator
223
 * \return Pointer to selection results (containing partial computed styles),
224
 *         or NULL on failure
225
 */
226
css_select_results *nscss_get_style(nscss_select_ctx *ctx, dom_node *n,
227
		uint64_t media, const css_stylesheet *inline_style,
228
		css_allocator_fn alloc, void *pw)
229
{
230
	css_select_results *styles;
231
	css_error error;
232
 
233
	error = css_select_style(ctx->ctx, n, media, inline_style,
234
			&selection_handler, ctx, &styles);
235
	if (error != CSS_OK) {
236
		return NULL;
237
	}
238
 
239
	return styles;
240
}
241
 
242
/**
243
 * Get an initial style
244
 *
245
 * \param ctx    CSS selection context
246
 * \param alloc  Memory allocation function
247
 * \param pw     Private word for allocator
248
 * \return Pointer to partial computed style, or NULL on failure
249
 */
250
css_computed_style *nscss_get_initial_style(nscss_select_ctx *ctx,
251
		css_allocator_fn alloc, void *pw)
252
{
253
	css_computed_style *style;
254
	css_error error;
255
 
256
	error = css_computed_style_create(alloc, pw, &style);
257
	if (error != CSS_OK)
258
		return NULL;
259
 
260
	error = css_computed_style_initialise(style, &selection_handler, ctx);
261
	if (error != CSS_OK) {
262
		css_computed_style_destroy(style);
263
		return NULL;
264
	}
265
 
266
	return style;
267
}
268
 
269
/**
270
 * Get a blank style
271
 *
272
 * \param ctx     CSS selection context
273
 * \param parent  Parent style to cascade inherited properties from
274
 * \param alloc   Memory allocation function
275
 * \param pw      Private word for allocator
276
 * \return Pointer to blank style, or NULL on failure
277
 */
278
css_computed_style *nscss_get_blank_style(nscss_select_ctx *ctx,
279
		const css_computed_style *parent,
280
		css_allocator_fn alloc, void *pw)
281
{
282
	css_computed_style *partial;
283
	css_error error;
284
 
285
	partial = nscss_get_initial_style(ctx, alloc, pw);
286
	if (partial == NULL)
287
		return NULL;
288
 
289
	error = css_computed_style_compose(parent, partial,
290
			nscss_compute_font_size, NULL, partial);
291
	if (error != CSS_OK) {
292
		css_computed_style_destroy(partial);
293
		return NULL;
294
	}
295
 
296
	return partial;
297
}
298
 
299
/**
300
 * Font size computation callback for libcss
301
 *
302
 * \param pw      Computation context
303
 * \param parent  Parent font size (absolute)
304
 * \param size    Font size to compute
305
 * \return CSS_OK on success
306
 *
307
 * \post \a size will be an absolute font size
308
 */
309
css_error nscss_compute_font_size(void *pw, const css_hint *parent,
310
		css_hint *size)
311
{
312
	/**
313
	 * Table of font-size keyword scale factors
314
	 *
315
	 * These are multiplied by the configured default font size
316
	 * to produce an absolute size for the relevant keyword
317
	 */
318
	static const css_fixed factors[] = {
319
		FLTTOFIX(0.5625), /* xx-small */
320
		FLTTOFIX(0.6250), /* x-small */
321
		FLTTOFIX(0.8125), /* small */
322
		FLTTOFIX(1.0000), /* medium */
323
		FLTTOFIX(1.1250), /* large */
324
		FLTTOFIX(1.5000), /* x-large */
325
		FLTTOFIX(2.0000)  /* xx-large */
326
	};
327
	css_hint_length parent_size;
328
 
329
	/* Grab parent size, defaulting to medium if none */
330
	if (parent == NULL) {
331
		parent_size.value = FDIV(FMUL(factors[CSS_FONT_SIZE_MEDIUM - 1],
332
					      INTTOFIX(nsoption_int(font_size))),
333
					 INTTOFIX(10));
334
		parent_size.unit = CSS_UNIT_PT;
335
	} else {
336
		assert(parent->status == CSS_FONT_SIZE_DIMENSION);
337
		assert(parent->data.length.unit != CSS_UNIT_EM);
338
		assert(parent->data.length.unit != CSS_UNIT_EX);
339
		assert(parent->data.length.unit != CSS_UNIT_PCT);
340
 
341
		parent_size = parent->data.length;
342
	}
343
 
5043 ashmew2 344
	/* assert(size->status != CSS_FONT_SIZE_INHERIT); */
3584 sourcerer 345
 
346
	if (size->status < CSS_FONT_SIZE_LARGER) {
347
		/* Keyword -- simple */
348
		size->data.length.value = FDIV(FMUL(factors[size->status - 1],
349
						    INTTOFIX(nsoption_int(font_size))),
350
					       F_10);
351
		size->data.length.unit = CSS_UNIT_PT;
352
	} else if (size->status == CSS_FONT_SIZE_LARGER) {
353
		/** \todo Step within table, if appropriate */
354
		size->data.length.value =
355
				FMUL(parent_size.value, FLTTOFIX(1.2));
356
		size->data.length.unit = parent_size.unit;
357
	} else if (size->status == CSS_FONT_SIZE_SMALLER) {
358
		/** \todo Step within table, if appropriate */
359
		size->data.length.value =
360
				FDIV(parent_size.value, FLTTOFIX(1.2));
361
		size->data.length.unit = parent_size.unit;
362
	} else if (size->data.length.unit == CSS_UNIT_EM ||
363
			size->data.length.unit == CSS_UNIT_EX) {
364
		size->data.length.value =
365
			FMUL(size->data.length.value, parent_size.value);
366
 
367
		if (size->data.length.unit == CSS_UNIT_EX) {
368
			/* 1ex = 0.6em in NetSurf */
369
			size->data.length.value = FMUL(size->data.length.value,
370
					FLTTOFIX(0.6));
371
		}
372
 
373
		size->data.length.unit = parent_size.unit;
374
	} else if (size->data.length.unit == CSS_UNIT_PCT) {
375
		size->data.length.value = FDIV(FMUL(size->data.length.value,
376
				parent_size.value), INTTOFIX(100));
377
		size->data.length.unit = parent_size.unit;
378
	}
379
 
380
	size->status = CSS_FONT_SIZE_DIMENSION;
381
 
382
	return CSS_OK;
383
}
384
 
385
/**
386
 * Parser for colours specified in attribute values.
387
 *
388
 * \param data    Data to parse (NUL-terminated)
389
 * \param result  Pointer to location to receive resulting css_color
390
 * \return true on success, false on invalid input
391
 */
392
bool nscss_parse_colour(const char *data, css_color *result)
393
{
394
	size_t len = strlen(data);
395
	uint8_t r, g, b;
396
 
397
	/* 2 */
398
	if (len == 0)
399
		return false;
400
 
401
	/* 3 */
402
	if (len == SLEN("transparent") && strcasecmp(data, "transparent") == 0)
403
		return false;
404
 
405
	/* 4 */
406
	if (parse_named_colour(data, result))
407
		return true;
408
 
409
	/** \todo Implement HTML5's utterly insane legacy colour parsing */
410
 
411
	if (data[0] == '#') {
412
		data++;
413
		len--;
414
	}
415
 
416
	if (len == 3 && isHex(data[0]) && isHex(data[1]) && isHex(data[2])) {
417
		r = charToHex(data[0]);
418
		g = charToHex(data[1]);
419
		b = charToHex(data[2]);
420
 
421
		r |= (r << 4);
422
		g |= (g << 4);
423
		b |= (b << 4);
424
 
425
		*result = (0xff << 24) | (r << 16) | (g << 8) | b;
426
 
427
		return true;
428
	} else if (len == 6 && isHex(data[0]) && isHex(data[1]) &&
429
			isHex(data[2]) && isHex(data[3]) && isHex(data[4]) &&
430
			isHex(data[5])) {
431
		r = (charToHex(data[0]) << 4) | charToHex(data[1]);
432
		g = (charToHex(data[2]) << 4) | charToHex(data[3]);
433
		b = (charToHex(data[4]) << 4) | charToHex(data[5]);
434
 
435
		*result = (0xff << 24) | (r << 16) | (g << 8) | b;
436
 
437
		return true;
438
	}
439
 
440
	return false;
441
}
442
 
443
/******************************************************************************
444
 * Style selection callbacks                                                  *
445
 ******************************************************************************/
446
 
447
/**
448
 * Callback to retrieve a node's name.
449
 *
450
 * \param pw     HTML document
451
 * \param node   DOM node
452
 * \param qname  Pointer to location to receive node name
453
 * \return CSS_OK on success,
454
 *         CSS_NOMEM on memory exhaustion.
455
 */
456
css_error node_name(void *pw, void *node, css_qname *qname)
457
{
458
	dom_node *n = node;
459
	dom_string *name;
460
	dom_exception err;
461
 
462
	err = dom_node_get_node_name(n, &name);
463
	if (err != DOM_NO_ERR)
464
		return CSS_NOMEM;
465
 
466
	qname->ns = NULL;
467
 
468
	err = dom_string_intern(name, &qname->name);
469
	if (err != DOM_NO_ERR) {
470
		dom_string_unref(name);
471
		return CSS_NOMEM;
472
	}
473
 
474
	dom_string_unref(name);
475
 
476
	return CSS_OK;
477
}
478
 
479
/**
480
 * Callback to retrieve a node's classes.
481
 *
482
 * \param pw         HTML document
483
 * \param node       DOM node
484
 * \param classes    Pointer to location to receive class name array
485
 * \param n_classes  Pointer to location to receive length of class name array
486
 * \return CSS_OK on success,
487
 *         CSS_NOMEM on memory exhaustion.
488
 *
489
 * \note The returned array will be destroyed by libcss. Therefore, it must
490
 *       be allocated using the same allocator as used by libcss during style
491
 *       selection.
492
 */
493
css_error node_classes(void *pw, void *node,
494
		lwc_string ***classes, uint32_t *n_classes)
495
{
496
	dom_node *n = node;
497
	dom_exception err;
498
 
499
	*classes = NULL;
500
	*n_classes = 0;
501
 
502
	err = dom_element_get_classes(n, classes, n_classes);
503
	if (err != DOM_NO_ERR)
504
		return CSS_NOMEM;
505
 
506
	return CSS_OK;
507
}
508
 
509
/**
510
 * Callback to retrieve a node's ID.
511
 *
512
 * \param pw    HTML document
513
 * \param node  DOM node
514
 * \param id    Pointer to location to receive id value
515
 * \return CSS_OK on success,
516
 *         CSS_NOMEM on memory exhaustion.
517
 */
518
css_error node_id(void *pw, void *node, lwc_string **id)
519
{
520
	dom_node *n = node;
521
	dom_string *attr;
522
	dom_exception err;
523
 
524
	*id = NULL;
525
 
526
	/** \todo Assumes an HTML DOM */
527
	err = dom_html_element_get_id(n, &attr);
528
	if (err != DOM_NO_ERR)
529
		return CSS_NOMEM;
530
 
531
	if (attr != NULL) {
532
		err = dom_string_intern(attr, id);
533
		if (err != DOM_NO_ERR) {
534
			dom_string_unref(attr);
535
			return CSS_NOMEM;
536
		}
537
		dom_string_unref(attr);
538
	}
539
 
540
	return CSS_OK;
541
}
542
 
543
/**
544
 * Callback to find a named ancestor node.
545
 *
546
 * \param pw        HTML document
547
 * \param node      DOM node
548
 * \param qname     Node name to search for
549
 * \param ancestor  Pointer to location to receive ancestor
550
 * \return CSS_OK.
551
 *
552
 * \post \a ancestor will contain the result, or NULL if there is no match
553
 */
554
css_error named_ancestor_node(void *pw, void *node,
555
		const css_qname *qname, void **ancestor)
556
{
557
	dom_element_named_ancestor_node(node, qname->name,
558
			(struct dom_element **)ancestor);
559
 
560
	return CSS_OK;
561
}
562
 
563
/**
564
 * Callback to find a named parent node
565
 *
566
 * \param pw      HTML document
567
 * \param node    DOM node
568
 * \param qname   Node name to search for
569
 * \param parent  Pointer to location to receive parent
570
 * \return CSS_OK.
571
 *
572
 * \post \a parent will contain the result, or NULL if there is no match
573
 */
574
css_error named_parent_node(void *pw, void *node,
575
		const css_qname *qname, void **parent)
576
{
577
	dom_element_named_parent_node(node, qname->name,
578
			(struct dom_element **)parent);
579
 
580
	return CSS_OK;
581
}
582
 
583
/**
584
 * Callback to find a named sibling node.
585
 *
586
 * \param pw       HTML document
587
 * \param node     DOM node
588
 * \param qname    Node name to search for
589
 * \param sibling  Pointer to location to receive sibling
590
 * \return CSS_OK.
591
 *
592
 * \post \a sibling will contain the result, or NULL if there is no match
593
 */
594
css_error named_sibling_node(void *pw, void *node,
595
		const css_qname *qname, void **sibling)
596
{
597
	dom_node *n = node;
598
	dom_node *prev;
599
	dom_exception err;
600
 
601
	*sibling = NULL;
602
 
603
	/* Find sibling element */
604
	err = dom_node_get_previous_sibling(n, &n);
605
	if (err != DOM_NO_ERR)
606
		return CSS_OK;
607
 
608
	while (n != NULL) {
609
		dom_node_type type;
610
 
611
		err = dom_node_get_node_type(n, &type);
612
		if (err != DOM_NO_ERR) {
613
			dom_node_unref(n);
614
			return CSS_OK;
615
		}
616
 
617
		if (type == DOM_ELEMENT_NODE)
618
			break;
619
 
620
		err = dom_node_get_previous_sibling(n, &prev);
621
		if (err != DOM_NO_ERR) {
622
			dom_node_unref(n);
623
			return CSS_OK;
624
		}
625
 
626
		dom_node_unref(n);
627
		n = prev;
628
	}
629
 
630
	if (n != NULL) {
631
		dom_string *name;
632
 
633
		err = dom_node_get_node_name(n, &name);
634
		if (err != DOM_NO_ERR) {
635
			dom_node_unref(n);
636
			return CSS_OK;
637
		}
638
 
639
		dom_node_unref(n);
640
 
641
		if (dom_string_caseless_lwc_isequal(name, qname->name)) {
642
			*sibling = n;
643
		}
644
 
645
		dom_string_unref(name);
646
	}
647
 
648
	return CSS_OK;
649
}
650
 
651
/**
652
 * Callback to find a named generic sibling node.
653
 *
654
 * \param pw       HTML document
655
 * \param node     DOM node
656
 * \param qname    Node name to search for
657
 * \param sibling  Pointer to location to receive ancestor
658
 * \return CSS_OK.
659
 *
660
 * \post \a sibling will contain the result, or NULL if there is no match
661
 */
662
css_error named_generic_sibling_node(void *pw, void *node,
663
		const css_qname *qname, void **sibling)
664
{
665
	dom_node *n = node;
666
	dom_node *prev;
667
	dom_exception err;
668
 
669
	*sibling = NULL;
670
 
671
	err = dom_node_get_previous_sibling(n, &n);
672
	if (err != DOM_NO_ERR)
673
		return CSS_OK;
674
 
675
	while (n != NULL) {
676
		dom_node_type type;
677
		dom_string *name;
678
 
679
		err = dom_node_get_node_type(n, &type);
680
		if (err != DOM_NO_ERR) {
681
			dom_node_unref(n);
682
			return CSS_OK;
683
		}
684
 
685
		if (type == DOM_ELEMENT_NODE) {
686
			err = dom_node_get_node_name(n, &name);
687
			if (err != DOM_NO_ERR) {
688
				dom_node_unref(n);
689
				return CSS_OK;
690
			}
691
 
692
			if (dom_string_caseless_lwc_isequal(name,
693
					qname->name)) {
694
				dom_string_unref(name);
695
				dom_node_unref(n);
696
				*sibling = n;
697
				break;
698
			}
699
			dom_string_unref(name);
700
		}
701
 
702
		err = dom_node_get_previous_sibling(n, &prev);
703
		if (err != DOM_NO_ERR) {
704
			dom_node_unref(n);
705
			return CSS_OK;
706
		}
707
 
708
		dom_node_unref(n);
709
		n = prev;
710
	}
711
 
712
	return CSS_OK;
713
}
714
 
715
/**
716
 * Callback to retrieve the parent of a node.
717
 *
718
 * \param pw      HTML document
719
 * \param node    DOM node
720
 * \param parent  Pointer to location to receive parent
721
 * \return CSS_OK.
722
 *
723
 * \post \a parent will contain the result, or NULL if there is no match
724
 */
725
css_error parent_node(void *pw, void *node, void **parent)
726
{
727
	dom_element_parent_node(node, (struct dom_element **)parent);
728
 
729
	return CSS_OK;
730
}
731
 
732
/**
733
 * Callback to retrieve the preceding sibling of a node.
734
 *
735
 * \param pw       HTML document
736
 * \param node     DOM node
737
 * \param sibling  Pointer to location to receive sibling
738
 * \return CSS_OK.
739
 *
740
 * \post \a sibling will contain the result, or NULL if there is no match
741
 */
742
css_error sibling_node(void *pw, void *node, void **sibling)
743
{
744
	dom_node *n = node;
745
	dom_node *prev;
746
	dom_exception err;
747
 
748
	*sibling = NULL;
749
 
750
	/* Find sibling element */
751
	err = dom_node_get_previous_sibling(n, &n);
752
	if (err != DOM_NO_ERR)
753
		return CSS_OK;
754
 
755
	while (n != NULL) {
756
		dom_node_type type;
757
 
758
		err = dom_node_get_node_type(n, &type);
759
		if (err != DOM_NO_ERR) {
760
			dom_node_unref(n);
761
			return CSS_OK;
762
		}
763
 
764
		if (type == DOM_ELEMENT_NODE)
765
			break;
766
 
767
		err = dom_node_get_previous_sibling(n, &prev);
768
		if (err != DOM_NO_ERR) {
769
			dom_node_unref(n);
770
			return CSS_OK;
771
		}
772
 
773
		dom_node_unref(n);
774
		n = prev;
775
	}
776
 
777
	if (n != NULL) {
778
		/** \todo Sort out reference counting */
779
		dom_node_unref(n);
780
 
781
		*sibling = n;
782
	}
783
 
784
	return CSS_OK;
785
}
786
 
787
/**
788
 * Callback to determine if a node has the given name.
789
 *
790
 * \param pw     HTML document
791
 * \param node   DOM node
792
 * \param qname  Name to match
793
 * \param match  Pointer to location to receive result
794
 * \return CSS_OK.
795
 *
796
 * \post \a match will contain true if the node matches and false otherwise.
797
 */
798
css_error node_has_name(void *pw, void *node,
799
		const css_qname *qname, bool *match)
800
{
801
	nscss_select_ctx *ctx = pw;
802
	dom_node *n = node;
803
 
804
	if (lwc_string_isequal(qname->name, ctx->universal, match) == lwc_error_ok && *match == false) {
805
		dom_string *name;
806
		dom_exception err;
807
 
808
		err = dom_node_get_node_name(n, &name);
809
		if (err != DOM_NO_ERR)
810
			return CSS_OK;
811
 
812
		/* Element names are case insensitive in HTML */
813
		*match = dom_string_caseless_lwc_isequal(name, qname->name);
814
 
815
		dom_string_unref(name);
816
	}
817
 
818
	return CSS_OK;
819
}
820
 
821
/**
822
 * Callback to determine if a node has the given class.
823
 *
824
 * \param pw     HTML document
825
 * \param node   DOM node
826
 * \param name   Name to match
827
 * \param match  Pointer to location to receive result
828
 * \return CSS_OK.
829
 *
830
 * \post \a match will contain true if the node matches and false otherwise.
831
 */
832
css_error node_has_class(void *pw, void *node,
833
		lwc_string *name, bool *match)
834
{
835
	dom_node *n = node;
836
	dom_exception err;
837
 
838
	/** \todo: Ensure that libdom performs case-insensitive
839
	 * matching in quirks mode */
840
	err = dom_element_has_class(n, name, match);
841
 
842
	assert(err == DOM_NO_ERR);
843
 
844
	return CSS_OK;
845
}
846
 
847
/**
848
 * Callback to determine if a node has the given id.
849
 *
850
 * \param pw     HTML document
851
 * \param node   DOM node
852
 * \param name   Name to match
853
 * \param match  Pointer to location to receive result
854
 * \return CSS_OK.
855
 *
856
 * \post \a match will contain true if the node matches and false otherwise.
857
 */
858
css_error node_has_id(void *pw, void *node,
859
		lwc_string *name, bool *match)
860
{
861
	dom_node *n = node;
862
	dom_string *attr;
863
	dom_exception err;
864
 
865
	*match = false;
866
 
867
	/** \todo Assumes an HTML DOM */
868
	err = dom_html_element_get_id(n, &attr);
869
	if (err != DOM_NO_ERR)
870
		return CSS_OK;
871
 
872
	if (attr != NULL) {
873
		*match = dom_string_lwc_isequal(attr, name);
874
 
875
		dom_string_unref(attr);
876
	}
877
 
878
	return CSS_OK;
879
}
880
 
881
/**
882
 * Callback to determine if a node has an attribute with the given name.
883
 *
884
 * \param pw     HTML document
885
 * \param node   DOM node
886
 * \param qname  Name to match
887
 * \param match  Pointer to location to receive result
888
 * \return CSS_OK on success,
889
 *         CSS_NOMEM on memory exhaustion.
890
 *
891
 * \post \a match will contain true if the node matches and false otherwise.
892
 */
893
css_error node_has_attribute(void *pw, void *node,
894
		const css_qname *qname, bool *match)
895
{
896
	dom_node *n = node;
897
	dom_string *name;
898
	dom_exception err;
899
 
900
	err = dom_string_create_interned(
901
			(const uint8_t *) lwc_string_data(qname->name),
902
			lwc_string_length(qname->name), &name);
903
	if (err != DOM_NO_ERR)
904
		return CSS_NOMEM;
905
 
906
	err = dom_element_has_attribute(n, name, match);
907
	if (err != DOM_NO_ERR) {
908
		dom_string_unref(name);
909
		return CSS_OK;
910
	}
911
 
912
	dom_string_unref(name);
913
 
914
	return CSS_OK;
915
}
916
 
917
/**
918
 * Callback to determine if a node has an attribute with given name and value.
919
 *
920
 * \param pw     HTML document
921
 * \param node   DOM node
922
 * \param qname  Name to match
923
 * \param value  Value to match
924
 * \param match  Pointer to location to receive result
925
 * \return CSS_OK on success,
926
 *         CSS_NOMEM on memory exhaustion.
927
 *
928
 * \post \a match will contain true if the node matches and false otherwise.
929
 */
930
css_error node_has_attribute_equal(void *pw, void *node,
931
		const css_qname *qname, lwc_string *value,
932
		bool *match)
933
{
934
	dom_node *n = node;
935
	dom_string *name;
936
	dom_string *atr_val;
937
	dom_exception err;
938
 
939
	size_t vlen = lwc_string_length(value);
940
 
941
	if (vlen == 0) {
942
		*match = false;
943
		return CSS_OK;
944
	}
945
 
946
	err = dom_string_create_interned(
947
		(const uint8_t *) lwc_string_data(qname->name),
948
		lwc_string_length(qname->name), &name);
949
	if (err != DOM_NO_ERR)
950
		return CSS_NOMEM;
951
 
952
	err = dom_element_get_attribute(n, name, &atr_val);
953
	if ((err != DOM_NO_ERR) || (atr_val == NULL)) {
954
		dom_string_unref(name);
955
		*match = false;
956
		return CSS_OK;
957
	}
958
 
959
	dom_string_unref(name);
960
 
961
	*match = dom_string_caseless_lwc_isequal(atr_val, value);
962
 
963
	dom_string_unref(atr_val);
964
 
965
	return CSS_OK;
966
}
967
 
968
/**
969
 * Callback to determine if a node has an attribute with the given name whose
970
 * value dashmatches that given.
971
 *
972
 * \param pw     HTML document
973
 * \param node   DOM node
974
 * \param qname  Name to match
975
 * \param value  Value to match
976
 * \param match  Pointer to location to receive result
977
 * \return CSS_OK on success,
978
 *         CSS_NOMEM on memory exhaustion.
979
 *
980
 * \post \a match will contain true if the node matches and false otherwise.
981
 */
982
css_error node_has_attribute_dashmatch(void *pw, void *node,
983
		const css_qname *qname, lwc_string *value,
984
		bool *match)
985
{
986
	dom_node *n = node;
987
	dom_string *name;
988
	dom_string *atr_val;
989
	dom_exception err;
990
 
991
	size_t vlen = lwc_string_length(value);
992
 
993
	if (vlen == 0) {
994
		*match = false;
995
		return CSS_OK;
996
	}
997
 
998
	err = dom_string_create_interned(
999
		(const uint8_t *) lwc_string_data(qname->name),
1000
		lwc_string_length(qname->name), &name);
1001
	if (err != DOM_NO_ERR)
1002
		return CSS_NOMEM;
1003
 
1004
	err = dom_element_get_attribute(n, name, &atr_val);
1005
	if ((err != DOM_NO_ERR) || (atr_val == NULL)) {
1006
		dom_string_unref(name);
1007
		*match = false;
1008
		return CSS_OK;
1009
	}
1010
 
1011
	dom_string_unref(name);
1012
 
1013
	/* check for exact match */
1014
	*match = dom_string_caseless_lwc_isequal(atr_val, value);
1015
 
1016
	/* check for dashmatch */
1017
	if (*match == false) {
1018
		const char *vdata = lwc_string_data(value);
1019
		const char *data = (const char *) dom_string_data(atr_val);
1020
		size_t len = dom_string_byte_length(atr_val);
1021
 
1022
		if (len > vlen && data[vlen] == '-' &&
1023
		    strncasecmp(data, vdata, vlen) == 0) {
1024
				*match = true;
1025
		}
1026
	}
1027
 
1028
	dom_string_unref(atr_val);
1029
 
1030
	return CSS_OK;
1031
}
1032
 
1033
/**
1034
 * Callback to determine if a node has an attribute with the given name whose
1035
 * value includes that given.
1036
 *
1037
 * \param pw     HTML document
1038
 * \param node   DOM node
1039
 * \param qname  Name to match
1040
 * \param value  Value to match
1041
 * \param match  Pointer to location to receive result
1042
 * \return CSS_OK on success,
1043
 *         CSS_NOMEM on memory exhaustion.
1044
 *
1045
 * \post \a match will contain true if the node matches and false otherwise.
1046
 */
1047
css_error node_has_attribute_includes(void *pw, void *node,
1048
		const css_qname *qname, lwc_string *value,
1049
		bool *match)
1050
{
1051
	dom_node *n = node;
1052
	dom_string *name;
1053
	dom_string *atr_val;
1054
	dom_exception err;
1055
	size_t vlen = lwc_string_length(value);
1056
	const char *p;
1057
	const char *start;
1058
	const char *end;
1059
 
1060
	*match = false;
1061
 
1062
	if (vlen == 0) {
1063
		return CSS_OK;
1064
	}
1065
 
1066
	err = dom_string_create_interned(
1067
		(const uint8_t *) lwc_string_data(qname->name),
1068
		lwc_string_length(qname->name), &name);
1069
	if (err != DOM_NO_ERR)
1070
		return CSS_NOMEM;
1071
 
1072
	err = dom_element_get_attribute(n, name, &atr_val);
1073
	if ((err != DOM_NO_ERR) || (atr_val == NULL)) {
1074
		dom_string_unref(name);
1075
		*match = false;
1076
		return CSS_OK;
1077
	}
1078
 
1079
	dom_string_unref(name);
1080
 
1081
	/* check for match */
1082
	start = (const char *) dom_string_data(atr_val);
1083
	end = start + dom_string_byte_length(atr_val);
1084
 
1085
	for (p = start; p <= end; p++) {
1086
		if (*p == ' ' || *p == '\0') {
1087
			if ((size_t) (p - start) == vlen &&
1088
			    strncasecmp(start,
1089
					lwc_string_data(value),
1090
					vlen) == 0) {
1091
				*match = true;
1092
				break;
1093
			}
1094
 
1095
			start = p + 1;
1096
		}
1097
	}
1098
 
1099
	dom_string_unref(atr_val);
1100
 
1101
	return CSS_OK;
1102
}
1103
 
1104
/**
1105
 * Callback to determine if a node has an attribute with the given name whose
1106
 * value has the prefix given.
1107
 *
1108
 * \param pw     HTML document
1109
 * \param node   DOM node
1110
 * \param qname  Name to match
1111
 * \param value  Value to match
1112
 * \param match  Pointer to location to receive result
1113
 * \return CSS_OK on success,
1114
 *         CSS_NOMEM on memory exhaustion.
1115
 *
1116
 * \post \a match will contain true if the node matches and false otherwise.
1117
 */
1118
css_error node_has_attribute_prefix(void *pw, void *node,
1119
		const css_qname *qname, lwc_string *value,
1120
		bool *match)
1121
{
1122
	dom_node *n = node;
1123
	dom_string *name;
1124
	dom_string *atr_val;
1125
	dom_exception err;
1126
 
1127
	size_t vlen = lwc_string_length(value);
1128
 
1129
	if (vlen == 0) {
1130
		*match = false;
1131
		return CSS_OK;
1132
	}
1133
 
1134
	err = dom_string_create_interned(
1135
		(const uint8_t *) lwc_string_data(qname->name),
1136
		lwc_string_length(qname->name), &name);
1137
	if (err != DOM_NO_ERR)
1138
		return CSS_NOMEM;
1139
 
1140
	err = dom_element_get_attribute(n, name, &atr_val);
1141
	if ((err != DOM_NO_ERR) || (atr_val == NULL)) {
1142
		dom_string_unref(name);
1143
		*match = false;
1144
		return CSS_OK;
1145
	}
1146
 
1147
	dom_string_unref(name);
1148
 
1149
	/* check for exact match */
1150
	*match = dom_string_caseless_lwc_isequal(atr_val, value);
1151
 
1152
	/* check for prefix match */
1153
	if (*match == false) {
1154
		const char *data = (const char *) dom_string_data(atr_val);
1155
		size_t len = dom_string_byte_length(atr_val);
1156
 
1157
		if ((len >= vlen) &&
1158
		    (strncasecmp(data, lwc_string_data(value), vlen) == 0)) {
1159
			*match = true;
1160
		}
1161
	}
1162
 
1163
	dom_string_unref(atr_val);
1164
 
1165
	return CSS_OK;
1166
}
1167
 
1168
/**
1169
 * Callback to determine if a node has an attribute with the given name whose
1170
 * value has the suffix given.
1171
 *
1172
 * \param pw     HTML document
1173
 * \param node   DOM node
1174
 * \param qname  Name to match
1175
 * \param value  Value to match
1176
 * \param match  Pointer to location to receive result
1177
 * \return CSS_OK on success,
1178
 *         CSS_NOMEM on memory exhaustion.
1179
 *
1180
 * \post \a match will contain true if the node matches and false otherwise.
1181
 */
1182
css_error node_has_attribute_suffix(void *pw, void *node,
1183
		const css_qname *qname, lwc_string *value,
1184
		bool *match)
1185
{
1186
	dom_node *n = node;
1187
	dom_string *name;
1188
	dom_string *atr_val;
1189
	dom_exception err;
1190
 
1191
	size_t vlen = lwc_string_length(value);
1192
 
1193
	if (vlen == 0) {
1194
		*match = false;
1195
		return CSS_OK;
1196
	}
1197
 
1198
	err = dom_string_create_interned(
1199
		(const uint8_t *) lwc_string_data(qname->name),
1200
		lwc_string_length(qname->name), &name);
1201
	if (err != DOM_NO_ERR)
1202
		return CSS_NOMEM;
1203
 
1204
	err = dom_element_get_attribute(n, name, &atr_val);
1205
	if ((err != DOM_NO_ERR) || (atr_val == NULL)) {
1206
		dom_string_unref(name);
1207
		*match = false;
1208
		return CSS_OK;
1209
	}
1210
 
1211
	dom_string_unref(name);
1212
 
1213
	/* check for exact match */
1214
	*match = dom_string_caseless_lwc_isequal(atr_val, value);
1215
 
1216
	/* check for prefix match */
1217
	if (*match == false) {
1218
		const char *data = (const char *) dom_string_data(atr_val);
1219
		size_t len = dom_string_byte_length(atr_val);
1220
 
1221
		const char *start = (char *) data + len - vlen;
1222
 
1223
		if ((len >= vlen) &&
1224
		    (strncasecmp(start, lwc_string_data(value), vlen) == 0)) {
1225
			*match = true;
1226
		}
1227
 
1228
 
1229
	}
1230
 
1231
	dom_string_unref(atr_val);
1232
 
1233
	return CSS_OK;
1234
}
1235
 
1236
/**
1237
 * Callback to determine if a node has an attribute with the given name whose
1238
 * value contains the substring given.
1239
 *
1240
 * \param pw     HTML document
1241
 * \param node   DOM node
1242
 * \param qname  Name to match
1243
 * \param value  Value to match
1244
 * \param match  Pointer to location to receive result
1245
 * \return CSS_OK on success,
1246
 *         CSS_NOMEM on memory exhaustion.
1247
 *
1248
 * \post \a match will contain true if the node matches and false otherwise.
1249
 */
1250
css_error node_has_attribute_substring(void *pw, void *node,
1251
		const css_qname *qname, lwc_string *value,
1252
		bool *match)
1253
{
1254
	dom_node *n = node;
1255
	dom_string *name;
1256
	dom_string *atr_val;
1257
	dom_exception err;
1258
 
1259
	size_t vlen = lwc_string_length(value);
1260
 
1261
	if (vlen == 0) {
1262
		*match = false;
1263
		return CSS_OK;
1264
	}
1265
 
1266
	err = dom_string_create_interned(
1267
		(const uint8_t *) lwc_string_data(qname->name),
1268
		lwc_string_length(qname->name), &name);
1269
	if (err != DOM_NO_ERR)
1270
		return CSS_NOMEM;
1271
 
1272
	err = dom_element_get_attribute(n, name, &atr_val);
1273
	if ((err != DOM_NO_ERR) || (atr_val == NULL)) {
1274
		dom_string_unref(name);
1275
		*match = false;
1276
		return CSS_OK;
1277
	}
1278
 
1279
	dom_string_unref(name);
1280
 
1281
	/* check for exact match */
1282
	*match = dom_string_caseless_lwc_isequal(atr_val, value);
1283
 
1284
	/* check for prefix match */
1285
	if (*match == false) {
1286
		const char *vdata = lwc_string_data(value);
1287
		const char *start = (const char *) dom_string_data(atr_val);
1288
		size_t len = dom_string_byte_length(atr_val);
1289
		const char *last_start = start + len - vlen;
1290
 
1291
		if (len >= vlen) {
1292
			while (start <= last_start) {
1293
				if (strncasecmp(start, vdata,
1294
						vlen) == 0) {
1295
					*match = true;
1296
					break;
1297
				}
1298
 
1299
				start++;
1300
			}
1301
		}
1302
	}
1303
 
1304
	dom_string_unref(atr_val);
1305
 
1306
	return CSS_OK;
1307
}
1308
 
1309
/**
1310
 * Callback to determine if a node is the root node of the document.
1311
 *
1312
 * \param pw     HTML document
1313
 * \param node   DOM node
1314
 * \param match  Pointer to location to receive result
1315
 * \return CSS_OK.
1316
 *
1317
 * \post \a match will contain true if the node matches and false otherwise.
1318
 */
1319
css_error node_is_root(void *pw, void *node, bool *match)
1320
{
1321
	dom_node *n = node;
1322
	dom_node *parent;
1323
	dom_node_type type;
1324
	dom_exception err;
1325
 
1326
	err = dom_node_get_parent_node(n, &parent);
1327
	if (err != DOM_NO_ERR) {
1328
		return CSS_NOMEM;
1329
	}
1330
 
1331
	if (parent != NULL) {
1332
		err = dom_node_get_node_type(parent, &type);
1333
 
1334
		dom_node_unref(parent);
1335
 
1336
		if (err != DOM_NO_ERR)
1337
			return CSS_NOMEM;
1338
 
1339
		if (type != DOM_DOCUMENT_NODE) {
1340
			*match = false;
1341
			return CSS_OK;
1342
		}
1343
	}
1344
 
1345
	*match = true;
1346
 
1347
	return CSS_OK;
1348
}
1349
 
1350
static int
1351
node_count_siblings_check(dom_node *node,
1352
			  bool check_name,
1353
			  dom_string *name)
1354
{
1355
	dom_node_type type;
1356
	int ret = 0;
1357
	dom_exception exc;
1358
 
1359
	if (node == NULL)
1360
		return 0;
1361
 
1362
	exc = dom_node_get_node_type(node, &type);
1363
	if ((exc != DOM_NO_ERR) || (type != DOM_ELEMENT_NODE)) {
1364
		return 0;
1365
	}
1366
 
1367
	if (check_name) {
1368
		dom_string *node_name = NULL;
1369
		exc = dom_node_get_node_name(node, &node_name);
1370
 
1371
		if ((exc == DOM_NO_ERR) && (node_name != NULL)) {
1372
 
1373
			if (dom_string_caseless_isequal(name,
1374
							node_name)) {
1375
				ret = 1;
1376
			}
1377
			dom_string_unref(node_name);
1378
		}
1379
	} else {
1380
		ret = 1;
1381
	}
1382
 
1383
	return ret;
1384
}
1385
 
1386
/**
1387
 * Callback to count a node's siblings.
1388
 *
1389
 * \param pw         HTML document
1390
 * \param node       DOM node
1391
 * \param same_name  Only count siblings with the same name, or all
1392
 * \param after      Count anteceding instead of preceding siblings
1393
 * \param count      Pointer to location to receive result
1394
 * \return CSS_OK.
1395
 *
1396
 * \post \a count will contain the number of siblings
1397
 */
1398
css_error node_count_siblings(void *pw, void *n, bool same_name,
1399
		bool after, int32_t *count)
1400
{
1401
	int32_t cnt = 0;
1402
	dom_exception exc;
1403
	dom_string *node_name = NULL;
1404
 
1405
	if (same_name) {
1406
		dom_node *node = n;
1407
		exc = dom_node_get_node_name(node, &node_name);
1408
		if ((exc != DOM_NO_ERR) || (node_name == NULL)) {
1409
			return CSS_NOMEM;
1410
		}
1411
	}
1412
 
1413
	if (after) {
1414
		dom_node *node = dom_node_ref(n);
1415
		dom_node *next;
1416
 
1417
		do {
1418
			exc = dom_node_get_next_sibling(node, &next);
1419
			if ((exc != DOM_NO_ERR))
1420
				break;
1421
 
1422
			dom_node_unref(node);
1423
			node = next;
1424
 
1425
			cnt += node_count_siblings_check(node, same_name, node_name);
1426
		} while (node != NULL);
1427
	} else {
1428
		dom_node *node = dom_node_ref(n);
1429
		dom_node *next;
1430
 
1431
		do {
1432
			exc = dom_node_get_previous_sibling(node, &next);
1433
			if ((exc != DOM_NO_ERR))
1434
				break;
1435
 
1436
			dom_node_unref(node);
1437
			node = next;
1438
 
1439
			cnt += node_count_siblings_check(node, same_name, node_name);
1440
 
1441
		} while (node != NULL);
1442
	}
1443
 
1444
	if (node_name != NULL) {
1445
		dom_string_unref(node_name);
1446
	}
1447
 
1448
	*count = cnt;
1449
	return CSS_OK;
1450
}
1451
 
1452
/**
1453
 * Callback to determine if a node is empty.
1454
 *
1455
 * \param pw     HTML document
1456
 * \param node   DOM node
1457
 * \param match  Pointer to location to receive result
1458
 * \return CSS_OK.
1459
 *
1460
 * \post \a match will contain true if the node is empty and false otherwise.
1461
 */
1462
css_error node_is_empty(void *pw, void *node, bool *match)
1463
{
1464
	dom_node *n = node, *next;
1465
	dom_exception err;
1466
 
1467
	*match = true;
1468
 
1469
	err = dom_node_get_first_child(n, &n);
1470
	if (err != DOM_NO_ERR) {
1471
		return CSS_BADPARM;
1472
	}
1473
 
1474
	while (n != NULL) {
1475
		dom_node_type ntype;
1476
		err = dom_node_get_node_type(n, &ntype);
1477
		if (err != DOM_NO_ERR) {
1478
			dom_node_unref(n);
1479
			return CSS_BADPARM;
1480
		}
1481
 
1482
		if (ntype == DOM_ELEMENT_NODE ||
1483
		    ntype == DOM_TEXT_NODE) {
1484
			*match = false;
1485
			dom_node_unref(n);
1486
			break;
1487
		}
1488
 
1489
		err = dom_node_get_next_sibling(n, &next);
1490
		if (err != DOM_NO_ERR) {
1491
			dom_node_unref(n);
1492
			return CSS_BADPARM;
1493
		}
1494
		dom_node_unref(n);
1495
		n = next;
1496
	}
1497
 
1498
	return CSS_OK;
1499
}
1500
 
1501
/**
1502
 * Callback to determine if a node is a linking element.
1503
 *
1504
 * \param pw     HTML document
1505
 * \param n      DOM node
1506
 * \param match  Pointer to location to receive result
1507
 * \return CSS_OK.
1508
 *
1509
 * \post \a match will contain true if the node matches and false otherwise.
1510
 */
1511
css_error node_is_link(void *pw, void *n, bool *match)
1512
{
1513
	dom_node *node = n;
1514
	dom_exception exc;
1515
	dom_string *node_name = NULL;
1516
 
1517
	exc = dom_node_get_node_name(node, &node_name);
1518
	if ((exc != DOM_NO_ERR) || (node_name == NULL)) {
1519
		return CSS_NOMEM;
1520
	}
1521
 
1522
	if (dom_string_caseless_lwc_isequal(node_name, corestring_lwc_a)) {
1523
		bool has_href;
1524
		exc = dom_element_has_attribute(node, corestring_dom_href,
1525
				&has_href);
1526
		if ((exc == DOM_NO_ERR) && (has_href)) {
1527
			*match = true;
1528
		} else {
1529
			*match = false;
1530
		}
1531
	} else {
1532
		*match = false;
1533
	}
1534
	dom_string_unref(node_name);
1535
 
1536
	return CSS_OK;
1537
}
1538
 
1539
/**
1540
 * Callback to determine if a node is a linking element whose target has been
1541
 * visited.
1542
 *
1543
 * \param pw     HTML document
1544
 * \param node   DOM node
1545
 * \param match  Pointer to location to receive result
1546
 * \return CSS_OK.
1547
 *
1548
 * \post \a match will contain true if the node matches and false otherwise.
1549
 */
1550
css_error node_is_visited(void *pw, void *node, bool *match)
1551
{
1552
	*match = false;
1553
 
1554
	/** \todo Implement visted check in a more performant way */
1555
 
1556
#ifdef SUPPORT_VISITED
1557
	nscss_select_ctx *ctx = pw;
1558
	xmlNode *n = node;
1559
 
1560
	if (strcasecmp((const char *) n->name, "a") == 0) {
1561
		nsurl *url;
1562
		nserror error;
1563
		const struct url_data *data;
1564
		xmlChar *href = xmlGetProp(n, (const xmlChar *) "href");
1565
 
1566
		if (href == NULL)
1567
			return CSS_OK;
1568
 
1569
		/* Make href absolute */
1570
		/* TODO: this duplicates what we do for box->href */
1571
		error = nsurl_join(ctx->base_url, (const char *)href, &url);
1572
 
1573
		xmlFree(href);
1574
		if (error != NSERROR_OK) {
1575
			return CSS_NOMEM;
1576
		}
1577
 
1578
		data = urldb_get_url_data(nsurl_access(url));
1579
 
1580
		/* Visited if in the db and has
1581
		 * non-zero visit count */
1582
		if (data != NULL && data->visits > 0)
1583
			*match = true;
1584
 
1585
		nsurl_unref(url);
1586
	}
1587
#endif
1588
 
1589
	return CSS_OK;
1590
}
1591
 
1592
/**
1593
 * Callback to determine if a node is currently being hovered over.
1594
 *
1595
 * \param pw     HTML document
1596
 * \param node   DOM node
1597
 * \param match  Pointer to location to receive result
1598
 * \return CSS_OK.
1599
 *
1600
 * \post \a match will contain true if the node matches and false otherwise.
1601
 */
1602
css_error node_is_hover(void *pw, void *node, bool *match)
1603
{
1604
	/** \todo Support hovering */
1605
 
1606
	*match = false;
1607
 
1608
	return CSS_OK;
1609
}
1610
 
1611
/**
1612
 * Callback to determine if a node is currently activated.
1613
 *
1614
 * \param pw     HTML document
1615
 * \param node   DOM node
1616
 * \param match  Pointer to location to receive result
1617
 * \return CSS_OK.
1618
 *
1619
 * \post \a match will contain true if the node matches and false otherwise.
1620
 */
1621
css_error node_is_active(void *pw, void *node, bool *match)
1622
{
1623
	/** \todo Support active nodes */
1624
 
1625
	*match = false;
1626
 
1627
	return CSS_OK;
1628
}
1629
 
1630
/**
1631
 * Callback to determine if a node has the input focus.
1632
 *
1633
 * \param pw     HTML document
1634
 * \param node   DOM node
1635
 * \param match  Pointer to location to receive result
1636
 * \return CSS_OK.
1637
 *
1638
 * \post \a match will contain true if the node matches and false otherwise.
1639
 */
1640
css_error node_is_focus(void *pw, void *node, bool *match)
1641
{
1642
	/** \todo Support focussed nodes */
1643
 
1644
	*match = false;
1645
 
1646
	return CSS_OK;
1647
}
1648
 
1649
/**
1650
 * Callback to determine if a node is enabled.
1651
 *
1652
 * \param pw     HTML document
1653
 * \param node   DOM node
1654
 * \param match  Pointer to location to receive result
1655
 * \return CSS_OK.
1656
 *
1657
 * \post \a match with contain true if the node is enabled and false otherwise.
1658
 */
1659
css_error node_is_enabled(void *pw, void *node, bool *match)
1660
{
1661
	/** \todo Support enabled nodes */
1662
 
1663
	*match = false;
1664
 
1665
	return CSS_OK;
1666
}
1667
 
1668
/**
1669
 * Callback to determine if a node is disabled.
1670
 *
1671
 * \param pw     HTML document
1672
 * \param node   DOM node
1673
 * \param match  Pointer to location to receive result
1674
 * \return CSS_OK.
1675
 *
1676
 * \post \a match with contain true if the node is disabled and false otherwise.
1677
 */
1678
css_error node_is_disabled(void *pw, void *node, bool *match)
1679
{
1680
	/** \todo Support disabled nodes */
1681
 
1682
	*match = false;
1683
 
1684
	return CSS_OK;
1685
}
1686
 
1687
/**
1688
 * Callback to determine if a node is checked.
1689
 *
1690
 * \param pw     HTML document
1691
 * \param node   DOM node
1692
 * \param match  Pointer to location to receive result
1693
 * \return CSS_OK.
1694
 *
1695
 * \post \a match with contain true if the node is checked and false otherwise.
1696
 */
1697
css_error node_is_checked(void *pw, void *node, bool *match)
1698
{
1699
	/** \todo Support checked nodes */
1700
 
1701
	*match = false;
1702
 
1703
	return CSS_OK;
1704
}
1705
 
1706
/**
1707
 * Callback to determine if a node is the target of the document URL.
1708
 *
1709
 * \param pw     HTML document
1710
 * \param node   DOM node
1711
 * \param match  Pointer to location to receive result
1712
 * \return CSS_OK.
1713
 *
1714
 * \post \a match with contain true if the node matches and false otherwise.
1715
 */
1716
css_error node_is_target(void *pw, void *node, bool *match)
1717
{
1718
	/** \todo Support target */
1719
 
1720
	*match = false;
1721
 
1722
	return CSS_OK;
1723
}
1724
 
1725
/**
1726
 * Callback to determine if a node has the given language
1727
 *
1728
 * \param pw     HTML document
1729
 * \param node   DOM node
1730
 * \param lang   Language specifier to match
1731
 * \param match  Pointer to location to receive result
1732
 * \return CSS_OK.
1733
 *
1734
 * \post \a match will contain true if the node matches and false otherwise.
1735
 */
1736
css_error node_is_lang(void *pw, void *node,
1737
		lwc_string *lang, bool *match)
1738
{
1739
	/** \todo Support languages */
1740
 
1741
	*match = false;
1742
 
1743
	return CSS_OK;
1744
}
1745
 
1746
static css_error
1747
node_presentational_hint_vertical_align(nscss_select_ctx *ctx,
1748
					  dom_node *node,
1749
					  css_hint *hint)
1750
{
1751
	dom_string *name;
1752
	dom_string *valign = NULL;
1753
	dom_exception err;
1754
 
1755
	err = dom_node_get_node_name(node, &name);
1756
	if (err != DOM_NO_ERR)
1757
		return CSS_PROPERTY_NOT_SET;
1758
 
1759
	if (dom_string_caseless_lwc_isequal(name, corestring_lwc_col) ||
1760
	    dom_string_caseless_lwc_isequal(name, corestring_lwc_thead) ||
1761
	    dom_string_caseless_lwc_isequal(name, corestring_lwc_tbody) ||
1762
	    dom_string_caseless_lwc_isequal(name, corestring_lwc_tfoot) ||
1763
	    dom_string_caseless_lwc_isequal(name, corestring_lwc_tr) ||
1764
	    dom_string_caseless_lwc_isequal(name, corestring_lwc_td) ||
1765
	    dom_string_caseless_lwc_isequal(name, corestring_lwc_th)) {
1766
		err = dom_element_get_attribute(node,
1767
				corestring_dom_valign, &valign);
1768
		if (err != DOM_NO_ERR || valign == NULL) {
1769
			dom_string_unref(name);
1770
			return CSS_PROPERTY_NOT_SET;
1771
		}
1772
 
1773
		if (dom_string_caseless_lwc_isequal(valign,
1774
				corestring_lwc_top)) {
1775
			hint->status = CSS_VERTICAL_ALIGN_TOP;
1776
		} else if (dom_string_caseless_lwc_isequal(valign,
1777
					corestring_lwc_middle)) {
1778
			hint->status = CSS_VERTICAL_ALIGN_MIDDLE;
1779
		} else if (dom_string_caseless_lwc_isequal(valign,
1780
					corestring_lwc_bottom)) {
1781
			hint->status = CSS_VERTICAL_ALIGN_BOTTOM;
1782
		} else if (dom_string_caseless_lwc_isequal(valign,
1783
					corestring_lwc_baseline)) {
1784
			hint->status = CSS_VERTICAL_ALIGN_BASELINE;
1785
		} else {
1786
			dom_string_unref(valign);
1787
			dom_string_unref(name);
1788
			return CSS_PROPERTY_NOT_SET;
1789
		}
1790
 
1791
		dom_string_unref(valign);
1792
		dom_string_unref(name);
1793
 
1794
		return CSS_OK;
1795
	} else if (dom_string_caseless_lwc_isequal(name,
1796
				corestring_lwc_applet) ||
1797
		   dom_string_caseless_lwc_isequal(name,
1798
		   		corestring_lwc_embed) ||
1799
		   dom_string_caseless_lwc_isequal(name,
1800
		   		corestring_lwc_iframe) ||
1801
		   dom_string_caseless_lwc_isequal(name,
1802
		   		corestring_lwc_img) ||
1803
		   dom_string_caseless_lwc_isequal(name,
1804
		   		corestring_lwc_object)) {
1805
		/** \todo input[type=image][align=*] - $11.3.3 */
1806
		err = dom_element_get_attribute(node,
1807
				corestring_dom_align, &valign);
1808
		if (err != DOM_NO_ERR || valign == NULL) {
1809
			dom_string_unref(name);
1810
			return CSS_PROPERTY_NOT_SET;
1811
		}
1812
 
1813
		if (dom_string_caseless_lwc_isequal(valign,
1814
				corestring_lwc_top)) {
1815
			hint->status = CSS_VERTICAL_ALIGN_TOP;
1816
		} else if (dom_string_caseless_lwc_isequal(valign,
1817
					corestring_lwc_bottom) ||
1818
			   dom_string_caseless_lwc_isequal(valign,
1819
					corestring_lwc_baseline)) {
1820
			hint->status = CSS_VERTICAL_ALIGN_BASELINE;
1821
		} else if (dom_string_caseless_lwc_isequal(valign,
1822
					corestring_lwc_texttop)) {
1823
			hint->status = CSS_VERTICAL_ALIGN_TEXT_TOP;
1824
		} else if (dom_string_caseless_lwc_isequal(valign,
1825
					corestring_lwc_absmiddle) ||
1826
			   dom_string_caseless_lwc_isequal(valign,
1827
					corestring_lwc_abscenter)) {
1828
			hint->status = CSS_VERTICAL_ALIGN_MIDDLE;
1829
		} else {
1830
			dom_string_unref(valign);
1831
			dom_string_unref(name);
1832
			return CSS_PROPERTY_NOT_SET;
1833
		}
1834
 
1835
		dom_string_unref(valign);
1836
		dom_string_unref(name);
1837
 
1838
		return CSS_OK;
1839
	}
1840
 
1841
	dom_string_unref(name);
1842
	return CSS_PROPERTY_NOT_SET;
1843
}
1844
 
1845
static css_error
1846
node_presentational_hint_text_align(nscss_select_ctx *ctx,
1847
					  dom_node *node,
1848
					  css_hint *hint)
1849
{
1850
	dom_string *name;
1851
	dom_string *align = NULL;
1852
	dom_exception err;
1853
 
1854
	err = dom_node_get_node_name(node, &name);
1855
	if (err != DOM_NO_ERR)
1856
		return CSS_PROPERTY_NOT_SET;
1857
 
1858
	if (dom_string_caseless_lwc_isequal(name, corestring_lwc_p) ||
1859
	    dom_string_caseless_lwc_isequal(name, corestring_lwc_h1) ||
1860
	    dom_string_caseless_lwc_isequal(name, corestring_lwc_h2) ||
1861
	    dom_string_caseless_lwc_isequal(name, corestring_lwc_h3) ||
1862
	    dom_string_caseless_lwc_isequal(name, corestring_lwc_h4) ||
1863
	    dom_string_caseless_lwc_isequal(name, corestring_lwc_h5) ||
1864
	    dom_string_caseless_lwc_isequal(name, corestring_lwc_h6)) {
1865
		err = dom_element_get_attribute(node,
1866
				corestring_dom_align, &align);
1867
		if (err != DOM_NO_ERR || align == NULL) {
1868
			dom_string_unref(name);
1869
			return CSS_PROPERTY_NOT_SET;
1870
		}
1871
 
1872
		if (dom_string_caseless_lwc_isequal(align,
1873
				corestring_lwc_left)) {
1874
			hint->status = CSS_TEXT_ALIGN_LEFT;
1875
		} else if (dom_string_caseless_lwc_isequal(align,
1876
					corestring_lwc_center)) {
1877
			hint->status = CSS_TEXT_ALIGN_CENTER;
1878
		} else if (dom_string_caseless_lwc_isequal(align,
1879
					corestring_lwc_right)) {
1880
			hint->status = CSS_TEXT_ALIGN_RIGHT;
1881
		} else if (dom_string_caseless_lwc_isequal(align,
1882
					corestring_lwc_justify)) {
1883
			hint->status = CSS_TEXT_ALIGN_JUSTIFY;
1884
		} else {
1885
			dom_string_unref(align);
1886
			dom_string_unref(name);
1887
			return CSS_PROPERTY_NOT_SET;
1888
		}
1889
 
1890
		dom_string_unref(align);
1891
		dom_string_unref(name);
1892
 
1893
		return CSS_OK;
1894
	} else if (dom_string_caseless_lwc_isequal(name,
1895
				corestring_lwc_center)) {
1896
		hint->status = CSS_TEXT_ALIGN_LIBCSS_CENTER;
1897
 
1898
		dom_string_unref(name);
1899
 
1900
		return CSS_OK;
1901
	} else if (dom_string_caseless_lwc_isequal(name,
1902
				corestring_lwc_caption)) {
1903
		err = dom_element_get_attribute(node,
1904
				corestring_dom_align, &align);
1905
		if (err != DOM_NO_ERR) {
1906
			dom_string_unref(name);
1907
			return CSS_PROPERTY_NOT_SET;
1908
		}
1909
 
1910
		if (align == NULL || dom_string_caseless_lwc_isequal(align,
1911
				corestring_lwc_center)) {
1912
			hint->status = CSS_TEXT_ALIGN_LIBCSS_CENTER;
1913
		} else if (dom_string_caseless_lwc_isequal(align,
1914
					corestring_lwc_left)) {
1915
			hint->status = CSS_TEXT_ALIGN_LIBCSS_LEFT;
1916
		} else if (dom_string_caseless_lwc_isequal(align,
1917
					corestring_lwc_right)) {
1918
			hint->status = CSS_TEXT_ALIGN_LIBCSS_RIGHT;
1919
		} else if (dom_string_caseless_lwc_isequal(align,
1920
					corestring_lwc_justify)) {
1921
			hint->status = CSS_TEXT_ALIGN_JUSTIFY;
1922
		} else {
1923
			dom_string_unref(align);
1924
			dom_string_unref(name);
1925
			return CSS_PROPERTY_NOT_SET;
1926
		}
1927
 
1928
		if (align != NULL)
1929
			dom_string_unref(align);
1930
		dom_string_unref(name);
1931
 
1932
		return CSS_OK;
1933
	} else if (dom_string_caseless_lwc_isequal(name,
1934
				corestring_lwc_div) ||
1935
		   dom_string_caseless_lwc_isequal(name,
1936
		   		corestring_lwc_thead) ||
1937
		   dom_string_caseless_lwc_isequal(name,
1938
		   		corestring_lwc_tbody) ||
1939
		   dom_string_caseless_lwc_isequal(name,
1940
		   		corestring_lwc_tfoot) ||
1941
		   dom_string_caseless_lwc_isequal(name,
1942
		   		corestring_lwc_tr) ||
1943
		   dom_string_caseless_lwc_isequal(name,
1944
		   		corestring_lwc_td) ||
1945
		   dom_string_caseless_lwc_isequal(name,
1946
		   		corestring_lwc_th)) {
1947
		err = dom_element_get_attribute(node,
1948
				corestring_dom_align, &align);
1949
		if (err != DOM_NO_ERR || align == NULL) {
1950
			dom_string_unref(name);
1951
			return CSS_PROPERTY_NOT_SET;
1952
		}
1953
 
1954
		if (dom_string_caseless_lwc_isequal(align,
1955
					corestring_lwc_center)) {
1956
			hint->status = CSS_TEXT_ALIGN_LIBCSS_CENTER;
1957
		} else if (dom_string_caseless_lwc_isequal(align,
1958
					corestring_lwc_left)) {
1959
			hint->status = CSS_TEXT_ALIGN_LIBCSS_LEFT;
1960
		} else if (dom_string_caseless_lwc_isequal(align,
1961
					corestring_lwc_right)) {
1962
			hint->status = CSS_TEXT_ALIGN_LIBCSS_RIGHT;
1963
		} else if (dom_string_caseless_lwc_isequal(align,
1964
					corestring_lwc_justify)) {
1965
			hint->status = CSS_TEXT_ALIGN_JUSTIFY;
1966
		} else {
1967
			dom_string_unref(align);
1968
			dom_string_unref(name);
1969
			return CSS_PROPERTY_NOT_SET;
1970
		}
1971
 
1972
		dom_string_unref(align);
1973
		dom_string_unref(name);
1974
 
1975
		return CSS_OK;
1976
	} else if (dom_string_caseless_lwc_isequal(name,
1977
			corestring_lwc_table)) {
1978
		/* Tables usually reset alignment */
1979
		hint->status = CSS_TEXT_ALIGN_INHERIT_IF_NON_MAGIC;
1980
 
1981
		dom_string_unref(name);
1982
 
1983
		return CSS_OK;
1984
	} else {
1985
		dom_string_unref(name);
1986
 
1987
		return CSS_PROPERTY_NOT_SET;
1988
	}
1989
 
1990
}
1991
 
1992
static css_error
1993
node_presentational_hint_padding_trbl(nscss_select_ctx *ctx,
1994
					  dom_node *node,
1995
					  css_hint *hint)
1996
{
1997
	dom_string *name;
1998
	dom_exception exc;
1999
	dom_string *cellpadding = NULL;
2000
 
2001
	exc = dom_node_get_node_name(node, &name);
2002
	if (exc != DOM_NO_ERR)
2003
		return CSS_BADPARM;
2004
 
2005
	if (dom_string_caseless_lwc_isequal(name, corestring_lwc_td) ||
2006
	    dom_string_caseless_lwc_isequal(name, corestring_lwc_th)) {
2007
		css_qname qs;
2008
		dom_node *tablenode = NULL;
2009
		qs.ns = NULL;
2010
		qs.name = lwc_string_ref(corestring_lwc_table);
2011
		if (named_ancestor_node(ctx, node, &qs,
2012
					(void *)&tablenode) != CSS_OK) {
2013
			/* Didn't find, or had error */
2014
			dom_string_unref(name);
2015
			return CSS_PROPERTY_NOT_SET;
2016
		}
2017
 
2018
		lwc_string_unref(qs.name);
2019
 
2020
		if (tablenode != NULL) {
2021
			exc = dom_element_get_attribute(tablenode,
2022
					corestring_dom_cellpadding,
2023
					&cellpadding);
2024
			if (exc != DOM_NO_ERR) {
2025
				dom_string_unref(name);
2026
				return CSS_BADPARM;
2027
			}
2028
		}
2029
		/* No need to unref tablenode, named_ancestor_node does not
2030
		 * return a reffed node to the CSS
2031
		 */
2032
	}
2033
 
2034
	dom_string_unref(name);
2035
 
2036
	if (cellpadding == NULL)
2037
		return CSS_PROPERTY_NOT_SET;
2038
 
2039
	if (parse_dimension(dom_string_data(cellpadding), false,
2040
			    &hint->data.length.value,
2041
			    &hint->data.length.unit)) {
2042
		hint->status = CSS_PADDING_SET;
2043
	} else {
2044
		dom_string_unref(cellpadding);
2045
		return CSS_PROPERTY_NOT_SET;
2046
	}
2047
 
2048
	return CSS_OK;
2049
}
2050
 
2051
static css_error
2052
node_presentational_hint_margin_rl(nscss_select_ctx *ctx,
2053
				   dom_node *node,
2054
				   css_hint *hint,
2055
				   uint32_t property)
2056
{
2057
	dom_string *n;
2058
	dom_exception exc;
2059
 
2060
	exc = dom_node_get_node_name(node, &n);
2061
	if (exc != DOM_NO_ERR)
2062
		return CSS_BADPARM;
2063
 
2064
	if (dom_string_caseless_lwc_isequal(n, corestring_lwc_img) ||
2065
	    dom_string_caseless_lwc_isequal(n, corestring_lwc_applet)) {
2066
		dom_string_unref(n);
2067
		exc = dom_element_get_attribute(node,
2068
				corestring_dom_hspace, &n);
2069
		if (exc != DOM_NO_ERR) {
2070
			return CSS_BADPARM;
2071
		}
2072
 
2073
		if (n == NULL)
2074
			return CSS_PROPERTY_NOT_SET;
2075
 
2076
		if (parse_dimension(dom_string_data(n), false,
2077
				    &hint->data.length.value,
2078
				    &hint->data.length.unit)) {
2079
			hint->status = CSS_MARGIN_SET;
2080
		} else {
2081
			dom_string_unref(n);
2082
			return CSS_PROPERTY_NOT_SET;
2083
		}
2084
		dom_string_unref(n);
2085
		return CSS_OK;
2086
	} else if (dom_string_caseless_lwc_isequal(n, corestring_lwc_table) ||
2087
		   dom_string_caseless_lwc_isequal(n, corestring_lwc_align)) {
2088
		dom_string_unref(n);
2089
		exc = dom_element_get_attribute(node,
2090
				corestring_dom_align, &n);
2091
		if (exc != DOM_NO_ERR) {
2092
			return CSS_BADPARM;
2093
		}
2094
 
2095
		if (n == NULL)
2096
			return CSS_PROPERTY_NOT_SET;
2097
 
2098
		if (dom_string_caseless_lwc_isequal(n,
2099
				corestring_lwc_center) ||
2100
		    dom_string_caseless_lwc_isequal(n,
2101
		    		corestring_lwc_abscenter) ||
2102
		    dom_string_caseless_lwc_isequal(n,
2103
		    		corestring_lwc_middle) ||
2104
		    dom_string_caseless_lwc_isequal(n,
2105
		    		corestring_lwc_absmiddle)) {
2106
			hint->status = CSS_MARGIN_AUTO;
2107
		} else {
2108
			dom_string_unref(n);
2109
			return CSS_PROPERTY_NOT_SET;
2110
		}
2111
 
2112
		dom_string_unref(n);
2113
		return CSS_OK;
2114
	} else if (dom_string_caseless_lwc_isequal(n, corestring_lwc_hr)) {
2115
		dom_string_unref(n);
2116
		exc = dom_element_get_attribute(node,
2117
				corestring_dom_align, &n);
2118
		if (exc != DOM_NO_ERR)
2119
			return CSS_BADPARM;
2120
 
2121
		if (n == NULL)
2122
			return CSS_PROPERTY_NOT_SET;
2123
 
2124
		if (dom_string_caseless_lwc_isequal(n,
2125
				corestring_lwc_left)) {
2126
			if (property == CSS_PROP_MARGIN_LEFT) {
2127
				hint->data.length.value = 0;
2128
				hint->data.length.unit = CSS_UNIT_PX;
2129
				hint->status = CSS_MARGIN_SET;
2130
			} else {
2131
				hint->status = CSS_MARGIN_AUTO;
2132
			}
2133
		} else if (dom_string_caseless_lwc_isequal(n,
2134
				corestring_lwc_center)) {
2135
			hint->status = CSS_MARGIN_AUTO;
2136
		} else if (dom_string_caseless_lwc_isequal(n,
2137
				corestring_lwc_right)) {
2138
			if (property == CSS_PROP_MARGIN_RIGHT) {
2139
				hint->data.length.value = 0;
2140
				hint->data.length.unit = CSS_UNIT_PX;
2141
				hint->status = CSS_MARGIN_SET;
2142
			} else {
2143
				hint->status = CSS_MARGIN_AUTO;
2144
			}
2145
		} else {
2146
			dom_string_unref(n);
2147
			return CSS_PROPERTY_NOT_SET;
2148
		}
2149
		dom_string_unref(n);
2150
 
2151
		return CSS_OK;
2152
	}
2153
 
2154
	dom_string_unref(n);
2155
 
2156
	return CSS_PROPERTY_NOT_SET;
2157
}
2158
 
2159
static css_error
2160
node_presentational_hint_margin_tb(nscss_select_ctx *ctx,
2161
					  dom_node *node,
2162
					  css_hint *hint)
2163
{
2164
	dom_string *name, *vspace = NULL;
2165
	dom_exception exc;
2166
 
2167
	exc = dom_node_get_node_name(node, &name);
2168
	if (exc != DOM_NO_ERR)
2169
		return CSS_BADPARM;
2170
 
2171
	if (dom_string_caseless_lwc_isequal(name, corestring_lwc_img) ||
2172
	    dom_string_caseless_lwc_isequal(name, corestring_lwc_applet)) {
2173
		exc = dom_element_get_attribute(node, corestring_dom_vspace,
2174
				&vspace);
2175
		if (exc != DOM_NO_ERR) {
2176
			dom_string_unref(name);
2177
			return CSS_BADPARM;
2178
		}
2179
	}
2180
 
2181
	dom_string_unref(name);
2182
 
2183
	if (vspace == NULL)
2184
		return CSS_PROPERTY_NOT_SET;
2185
 
2186
	if (parse_dimension(dom_string_data(vspace), false,
2187
			    &hint->data.length.value,
2188
			    &hint->data.length.unit)) {
2189
		hint->status = CSS_MARGIN_SET;
2190
	} else {
2191
		dom_string_unref(vspace);
2192
		return CSS_PROPERTY_NOT_SET;
2193
	}
2194
 
2195
	dom_string_unref(vspace);
2196
 
2197
	return CSS_OK;
2198
}
2199
 
2200
static css_error
2201
node_presentational_hint_border_trbl_width(nscss_select_ctx *ctx,
2202
					  dom_node *node,
2203
					  css_hint *hint)
2204
{
2205
	dom_string *name;
2206
	dom_exception exc;
2207
	dom_string *width = NULL;
2208
	bool is_table_cell = false;
2209
 
2210
	exc = dom_node_get_node_name(node, &name);
2211
	if (exc != DOM_NO_ERR)
2212
		return CSS_BADPARM;
2213
 
2214
	if (dom_string_caseless_lwc_isequal(name, corestring_lwc_td) ||
2215
	    dom_string_caseless_lwc_isequal(name, corestring_lwc_th)) {
2216
		css_qname qs;
2217
		dom_node *tablenode = NULL;
2218
		qs.ns = NULL;
2219
		qs.name = lwc_string_ref(corestring_lwc_table);
2220
		if (named_ancestor_node(ctx, node, &qs,
2221
					(void *)&tablenode) != CSS_OK) {
2222
			/* Didn't find, or had error */
2223
			lwc_string_unref(qs.name);
2224
			dom_string_unref(name);
2225
			return CSS_PROPERTY_NOT_SET;
2226
		}
2227
 
2228
		lwc_string_unref(qs.name);
2229
		if (tablenode != NULL) {
2230
			exc = dom_element_get_attribute(tablenode,
2231
					corestring_dom_border, &width);
2232
			if (exc != DOM_NO_ERR) {
2233
				dom_string_unref(name);
2234
				return CSS_BADPARM;
2235
			}
2236
		}
2237
		/* No need to unref tablenode, named_ancestor_node does not
2238
		 * return a reffed node to the CSS
2239
		 */
2240
		is_table_cell = true;
2241
	} else if (dom_string_caseless_lwc_isequal(name,
2242
				corestring_lwc_table)) {
2243
		exc = dom_element_get_attribute(node, corestring_dom_border,
2244
				&width);
2245
		if (exc != DOM_NO_ERR) {
2246
			dom_string_unref(name);
2247
			return CSS_BADPARM;
2248
		}
2249
	}
2250
 
2251
	dom_string_unref(name);
2252
 
2253
	if (width == NULL)
2254
		return CSS_PROPERTY_NOT_SET;
2255
 
2256
	if (parse_dimension(dom_string_data(width), false,
2257
			    &hint->data.length.value,
2258
			    &hint->data.length.unit)) {
2259
		if (is_table_cell &&
2260
		    INTTOFIX(0) !=
2261
		    hint->data.length.value) {
2262
			hint->data.length.value = INTTOFIX(1);
2263
			hint->data.length.unit = CSS_UNIT_PX;
2264
		}
2265
		hint->status = CSS_BORDER_WIDTH_WIDTH;
2266
	} else {
2267
		dom_string_unref(width);
2268
		return CSS_PROPERTY_NOT_SET;
2269
	}
2270
 
2271
	dom_string_unref(width);
2272
 
2273
	return CSS_OK;
2274
}
2275
 
2276
static css_error
2277
node_presentational_hint_border_trbl_style(nscss_select_ctx *ctx,
2278
					  dom_node *node,
2279
					  css_hint *hint)
2280
{
2281
	dom_string *name;
2282
	dom_exception exc;
2283
 
2284
	exc = dom_node_get_node_name(node, &name);
2285
	if (exc != DOM_NO_ERR)
2286
		return CSS_BADPARM;
2287
 
2288
	if (dom_string_caseless_lwc_isequal(name, corestring_lwc_td) ||
2289
	    dom_string_caseless_lwc_isequal(name, corestring_lwc_th)) {
2290
		css_qname qs;
2291
		dom_node *tablenode = NULL;
2292
		qs.ns = NULL;
2293
		qs.name = lwc_string_ref(corestring_lwc_table);
2294
 
2295
		if (named_ancestor_node(ctx, node, &qs,
2296
					(void *)&tablenode) != CSS_OK) {
2297
			/* Didn't find, or had error */
2298
			lwc_string_unref(qs.name);
2299
			dom_string_unref(name);
2300
			return CSS_PROPERTY_NOT_SET;
2301
		}
2302
 
2303
		lwc_string_unref(qs.name);
2304
 
2305
		if (tablenode != NULL) {
2306
			bool has_border = false;
2307
 
2308
			exc = dom_element_has_attribute(tablenode,
2309
							corestring_dom_border,
2310
							&has_border);
2311
			if (exc != DOM_NO_ERR) {
2312
				dom_string_unref(name);
2313
				return CSS_BADPARM;
2314
			}
2315
 
2316
			if (has_border) {
2317
				hint->status = CSS_BORDER_STYLE_INSET;
2318
				dom_string_unref(name);
2319
				return CSS_OK;
2320
			}
2321
		}
2322
		/* No need to unref tablenode, named_ancestor_node does not
2323
		 * return a reffed node to the CSS
2324
		 */
2325
	} else if (dom_string_caseless_lwc_isequal(name,
2326
			corestring_lwc_table)) {
2327
		bool has_border = false;
2328
 
2329
		exc = dom_element_has_attribute(node,
2330
						corestring_dom_border,
2331
						&has_border);
2332
		if (exc != DOM_NO_ERR) {
2333
			dom_string_unref(name);
2334
			return CSS_BADPARM;
2335
		}
2336
 
2337
		if (has_border) {
2338
			hint->status = CSS_BORDER_STYLE_OUTSET;
2339
			dom_string_unref(name);
2340
			return CSS_OK;
2341
		}
2342
	}
2343
 
2344
	dom_string_unref(name);
2345
 
2346
	return CSS_PROPERTY_NOT_SET;
2347
}
2348
 
2349
static css_error
2350
node_presentational_hint_border_trbl_color(nscss_select_ctx *ctx,
2351
					  dom_node *node,
2352
					  css_hint *hint)
2353
{
2354
	dom_string *name;
2355
	dom_string *bordercolor = NULL;
2356
	dom_exception err;
2357
 
2358
	err = dom_node_get_node_name(node, &name);
2359
	if (err != DOM_NO_ERR)
2360
		return CSS_PROPERTY_NOT_SET;
2361
 
2362
	if (dom_string_caseless_lwc_isequal(name, corestring_lwc_td) ||
2363
	    dom_string_caseless_lwc_isequal(name, corestring_lwc_th)) {
2364
		css_qname qs;
2365
		dom_node *tablenode = NULL;
2366
		qs.ns = NULL;
2367
		qs.name = lwc_string_ref(corestring_lwc_table);
2368
 
2369
		if (named_ancestor_node(ctx, node, &qs,
2370
					(void *)&tablenode) != CSS_OK) {
2371
			/* Didn't find, or had error */
2372
			lwc_string_unref(qs.name);
2373
			dom_string_unref(name);
2374
			return CSS_PROPERTY_NOT_SET;
2375
		}
2376
 
2377
		lwc_string_unref(qs.name);
2378
 
2379
		if (tablenode != NULL) {
2380
			err = dom_element_get_attribute(node,
2381
					corestring_dom_bordercolor,
2382
					&bordercolor);
2383
		}
2384
		/* No need to unref tablenode, named_ancestor_node does not
2385
		 * return a reffed node to the CSS
2386
		 */
2387
 
2388
	} else if (dom_string_caseless_lwc_isequal(name,
2389
			corestring_lwc_table)) {
2390
		err = dom_element_get_attribute(node,
2391
				corestring_dom_bordercolor,
2392
				&bordercolor);
2393
	}
2394
 
2395
	dom_string_unref(name);
2396
 
2397
	if ((err != DOM_NO_ERR) || (bordercolor == NULL)) {
2398
		return CSS_PROPERTY_NOT_SET;
2399
	}
2400
 
2401
	if (nscss_parse_colour((const char *)dom_string_data(bordercolor),
2402
			       &hint->data.color)) {
2403
		hint->status = CSS_BORDER_COLOR_COLOR;
2404
		dom_string_unref(bordercolor);
2405
		return CSS_OK;
2406
	}
2407
 
2408
	dom_string_unref(bordercolor);
2409
	return CSS_PROPERTY_NOT_SET;
2410
}
2411
 
2412
static css_error
2413
node_presentational_hint_border_spacing(nscss_select_ctx *ctx,
2414
					  dom_node *node,
2415
					  css_hint *hint)
2416
{
2417
	dom_exception err;
2418
	dom_string *node_name = NULL;
2419
	dom_string *cellspacing = NULL;
2420
 
2421
	err = dom_node_get_node_name(node, &node_name);
2422
	if ((err != DOM_NO_ERR) || (node_name == NULL)) {
2423
		return CSS_PROPERTY_NOT_SET;
2424
	}
2425
 
2426
	if (!dom_string_caseless_lwc_isequal(node_name,
2427
			corestring_lwc_table)) {
2428
		dom_string_unref(node_name);
2429
		return CSS_PROPERTY_NOT_SET;
2430
	}
2431
 
2432
	dom_string_unref(node_name);
2433
 
2434
	err = dom_element_get_attribute(node,
2435
			corestring_dom_cellspacing, &cellspacing);
2436
	if ((err != DOM_NO_ERR) || (cellspacing == NULL)) {
2437
		return CSS_PROPERTY_NOT_SET;
2438
	}
2439
 
2440
 
2441
	if (parse_dimension((const char *)dom_string_data(cellspacing),
2442
			    false,
2443
			    &hint->data.position.h.value,
2444
			    &hint->data.position.h.unit)) {
2445
 
2446
		hint->data.position.v = hint->data.position.h;
2447
		hint->status = CSS_BORDER_SPACING_SET;
2448
 
2449
		dom_string_unref(cellspacing);
2450
		return CSS_OK;
2451
	}
2452
 
2453
	dom_string_unref(cellspacing);
2454
	return CSS_PROPERTY_NOT_SET;
2455
}
2456
 
2457
static css_error
2458
node_presentational_hint_width(nscss_select_ctx *ctx,
2459
					  dom_node *node,
2460
					  css_hint *hint)
2461
{
2462
	dom_string *name;
2463
	dom_string *width = NULL;
2464
	dom_exception err;
2465
	bool textarea = false;
2466
	bool input = false;
2467
 
2468
	err = dom_node_get_node_name(node, &name);
2469
	if (err != DOM_NO_ERR)
2470
		return CSS_PROPERTY_NOT_SET;
2471
 
2472
	if (dom_string_caseless_lwc_isequal(name, corestring_lwc_hr) ||
2473
	    dom_string_caseless_lwc_isequal(name, corestring_lwc_iframe) ||
2474
	    dom_string_caseless_lwc_isequal(name, corestring_lwc_img) ||
2475
	    dom_string_caseless_lwc_isequal(name, corestring_lwc_object) ||
2476
	    dom_string_caseless_lwc_isequal(name, corestring_lwc_table) ||
2477
	    dom_string_caseless_lwc_isequal(name, corestring_lwc_td) ||
2478
	    dom_string_caseless_lwc_isequal(name, corestring_lwc_th) ||
2479
	    dom_string_caseless_lwc_isequal(name, corestring_lwc_applet)) {
2480
		err = dom_element_get_attribute(node,
2481
				corestring_dom_width, &width);
2482
	} else if (dom_string_caseless_lwc_isequal(name,
2483
				corestring_lwc_textarea)) {
2484
		textarea = true;
2485
		err = dom_element_get_attribute(node,
2486
				corestring_dom_cols, &width);
2487
	} else if (dom_string_caseless_lwc_isequal(name,
2488
			corestring_lwc_input)) {
2489
		input = true;
2490
		err = dom_element_get_attribute(node,
2491
				corestring_dom_size, &width);
2492
	}
2493
 
2494
	dom_string_unref(name);
2495
 
2496
	if ((err != DOM_NO_ERR) || (width == NULL)) {
2497
		return CSS_PROPERTY_NOT_SET;
2498
	}
2499
 
2500
	if (parse_dimension((const char *)dom_string_data(width),
2501
			    false,
2502
			    &hint->data.length.value,
2503
			    &hint->data.length.unit)) {
2504
		hint->status = CSS_WIDTH_SET;
2505
		dom_string_unref(width);
2506
 
2507
		if (textarea) {
2508
			hint->data.length.unit = CSS_UNIT_EX;
2509
		}
2510
 
2511
		if (input) {
2512
			err = dom_element_get_attribute(node,
2513
					corestring_dom_type, &width);
2514
			if (err != DOM_NO_ERR) {
2515
				return CSS_PROPERTY_NOT_SET;
2516
			}
2517
 
2518
			if ((width == NULL) ||
2519
			    dom_string_caseless_lwc_isequal(width,
2520
					corestring_lwc_text) ||
2521
			    dom_string_caseless_lwc_isequal(width,
2522
					corestring_lwc_search) ||
2523
			    dom_string_caseless_lwc_isequal(width,
2524
					corestring_lwc_file) ||
2525
			    dom_string_caseless_lwc_isequal(width,
2526
			    		corestring_lwc_password)) {
2527
				hint->data.length.unit = CSS_UNIT_EX;
2528
			}
2529
			dom_string_unref(width);
2530
		}
2531
 
2532
		return CSS_OK;
2533
	}
2534
 
2535
	dom_string_unref(width);
2536
	return CSS_PROPERTY_NOT_SET;
2537
 
2538
}
2539
 
2540
static css_error
2541
node_presentational_hint_height(nscss_select_ctx *ctx,
2542
					  dom_node *node,
2543
					  css_hint *hint)
2544
{
2545
	dom_string *name;
2546
	dom_string *height = NULL;
2547
	dom_exception err;
2548
	bool textarea = false;
2549
 
2550
	err = dom_node_get_node_name(node, &name);
2551
	if (err != DOM_NO_ERR)
2552
		return CSS_PROPERTY_NOT_SET;
2553
 
2554
	if (dom_string_caseless_lwc_isequal(name, corestring_lwc_iframe) ||
2555
	    dom_string_caseless_lwc_isequal(name, corestring_lwc_td) ||
2556
	    dom_string_caseless_lwc_isequal(name, corestring_lwc_th) ||
2557
	    dom_string_caseless_lwc_isequal(name, corestring_lwc_tr) ||
2558
	    dom_string_caseless_lwc_isequal(name, corestring_lwc_img) ||
2559
	    dom_string_caseless_lwc_isequal(name, corestring_lwc_object) ||
2560
	    dom_string_caseless_lwc_isequal(name, corestring_lwc_applet)) {
2561
		err = dom_element_get_attribute(node,
2562
				corestring_dom_height, &height);
2563
	} else if (dom_string_caseless_lwc_isequal(name,
2564
			corestring_lwc_textarea)) {
2565
		textarea = true;
2566
		err = dom_element_get_attribute(node,
2567
				corestring_dom_rows, &height);
2568
	}
2569
 
2570
	dom_string_unref(name);
2571
 
2572
	if ((err != DOM_NO_ERR) || (height == NULL)) {
2573
		return CSS_PROPERTY_NOT_SET;
2574
	}
2575
 
2576
	if (parse_dimension((const char *)dom_string_data(height),
2577
			    false,
2578
			    &hint->data.length.value,
2579
			    &hint->data.length.unit)) {
2580
		hint->status = CSS_HEIGHT_SET;
2581
 
2582
		if (textarea) {
2583
			hint->data.length.unit = CSS_UNIT_EM;
2584
		}
2585
 
2586
		dom_string_unref(height);
2587
		return CSS_OK;
2588
	}
2589
 
2590
	dom_string_unref(height);
2591
	return CSS_PROPERTY_NOT_SET;
2592
}
2593
 
2594
static css_error
2595
node_presentational_hint_font_size(nscss_select_ctx *ctx,
2596
					  dom_node *node,
2597
					  css_hint *hint)
2598
{
2599
	dom_exception err;
2600
	dom_string *node_name = NULL;
2601
	dom_string *size;
2602
 
2603
	err = dom_node_get_node_name(node, &node_name);
2604
	if ((err != DOM_NO_ERR) || (node_name == NULL)) {
2605
		return CSS_NOMEM;
2606
	}
2607
 
2608
	if (!dom_string_caseless_lwc_isequal(node_name,
2609
			corestring_lwc_font)) {
2610
		dom_string_unref(node_name);
2611
		return CSS_PROPERTY_NOT_SET;
2612
	}
2613
 
2614
	dom_string_unref(node_name);
2615
 
2616
	err = dom_element_get_attribute(node, corestring_dom_size, &size);
2617
	if ((err != DOM_NO_ERR) || (size == NULL)) {
2618
		return CSS_PROPERTY_NOT_SET;
2619
	}
2620
 
2621
	if (parse_font_size((const char *)dom_string_data(size),
2622
			    &hint->status,
2623
			    &hint->data.length.value,
2624
			    &hint->data.length.unit)) {
2625
		dom_string_unref(size);
2626
		return CSS_OK;
2627
	}
2628
 
2629
	dom_string_unref(size);
2630
	return CSS_PROPERTY_NOT_SET;
2631
}
2632
 
2633
static css_error
2634
node_presentational_hint_float(nscss_select_ctx *ctx,
2635
					  dom_node *node,
2636
					  css_hint *hint)
2637
{
2638
	dom_exception err;
2639
	dom_string *node_name = NULL;
2640
	dom_string *align;
2641
 
2642
	err = dom_node_get_node_name(node, &node_name);
2643
	if ((err != DOM_NO_ERR) || (node_name == NULL)) {
2644
		return CSS_NOMEM;
2645
	}
2646
 
2647
	/** \todo input[type=image][align=*] - $11.3.3 */
2648
	if (!dom_string_caseless_lwc_isequal(node_name,
2649
			corestring_lwc_applet) &&
2650
	    !dom_string_caseless_lwc_isequal(node_name,
2651
	    		corestring_lwc_embed) &&
2652
	    !dom_string_caseless_lwc_isequal(node_name,
2653
	    		corestring_lwc_iframe) &&
2654
	    !dom_string_caseless_lwc_isequal(node_name,
2655
	    		corestring_lwc_img) &&
2656
	    !dom_string_caseless_lwc_isequal(node_name,
2657
	    		corestring_lwc_object)) {
2658
		dom_string_unref(node_name);
2659
		return CSS_PROPERTY_NOT_SET;
2660
	}
2661
 
2662
	dom_string_unref(node_name);
2663
 
2664
	err = dom_element_get_attribute(node, corestring_dom_align, &align);
2665
	if ((err != DOM_NO_ERR) || (align == NULL)) {
2666
		return CSS_PROPERTY_NOT_SET;
2667
	}
2668
 
2669
	if (dom_string_caseless_lwc_isequal(align,
2670
			corestring_lwc_left)) {
2671
		hint->status = CSS_FLOAT_LEFT;
2672
		dom_string_unref(align);
2673
		return CSS_OK;
2674
	} else if (dom_string_caseless_lwc_isequal(align,
2675
			corestring_lwc_right)) {
2676
		hint->status = CSS_FLOAT_RIGHT;
2677
		dom_string_unref(align);
2678
		return CSS_OK;
2679
	}
2680
 
2681
	dom_string_unref(align);
2682
 
2683
	return CSS_PROPERTY_NOT_SET;
2684
}
2685
 
2686
static css_error
2687
node_presentational_hint_color(nscss_select_ctx *ctx,
2688
					  dom_node *node,
2689
					  css_hint *hint)
2690
{
2691
	css_error error;
2692
	dom_exception err;
2693
	dom_string *node_name = NULL;
2694
	dom_string *color;
2695
 
2696
	err = dom_node_get_node_name(node, &node_name);
2697
	if ((err != DOM_NO_ERR) || (node_name == NULL)) {
2698
		return CSS_NOMEM;
2699
	}
2700
 
2701
	if (dom_string_caseless_lwc_isequal(node_name, corestring_lwc_a)) {
2702
		/* find body node */
2703
		css_qname qs;
2704
		dom_node *bodynode = NULL;
2705
		bool is_visited;
2706
 
2707
		qs.ns = NULL;
2708
		qs.name = lwc_string_ref(corestring_lwc_body);
2709
		if (named_ancestor_node(ctx, node, &qs,
2710
					(void *)&bodynode) != CSS_OK) {
2711
			/* Didn't find, or had error */
2712
			lwc_string_unref(qs.name);
2713
			dom_string_unref(node_name);
2714
			return CSS_PROPERTY_NOT_SET;
2715
		}
2716
 
2717
		lwc_string_unref(qs.name);
2718
 
2719
		/* deal with missing body ancestor */
2720
		if (bodynode == NULL) {
2721
			dom_string_unref(node_name);
2722
			return CSS_BADPARM;
2723
		}
2724
 
2725
		error = node_is_visited(ctx, node, &is_visited);
2726
		if (error != CSS_OK)
2727
			return error;
2728
 
2729
		if (is_visited) {
2730
			err = dom_element_get_attribute(bodynode,
2731
					corestring_dom_vlink, &color);
2732
			if ((err != DOM_NO_ERR) || (color == NULL)) {
2733
				dom_string_unref(node_name);
2734
				return CSS_PROPERTY_NOT_SET;
2735
			}
2736
		} else {
2737
			err = dom_element_get_attribute(bodynode,
2738
					corestring_dom_link, &color);
2739
			if ((err != DOM_NO_ERR) || (color == NULL)) {
2740
				dom_string_unref(node_name);
2741
				return CSS_PROPERTY_NOT_SET;
2742
			}
2743
		}
2744
	} else if (dom_string_caseless_lwc_isequal(node_name,
2745
			corestring_lwc_body)) {
2746
		err = dom_element_get_attribute(node,
2747
				corestring_dom_text, &color);
2748
		if ((err != DOM_NO_ERR) || (color == NULL)) {
2749
			dom_string_unref(node_name);
2750
			return CSS_PROPERTY_NOT_SET;
2751
		}
2752
	} else {
2753
		err = dom_element_get_attribute(node,
2754
				corestring_dom_color, &color);
2755
		if ((err != DOM_NO_ERR) || (color == NULL)) {
2756
			dom_string_unref(node_name);
2757
			return CSS_PROPERTY_NOT_SET;
2758
		}
2759
	}
2760
 
2761
	if (!nscss_parse_colour((const char *)dom_string_data(color),
2762
				&hint->data.color)) {
2763
		dom_string_unref(node_name);
2764
		return CSS_PROPERTY_NOT_SET;
2765
	}
2766
 
2767
	hint->status = CSS_COLOR_COLOR;
2768
 
2769
	dom_string_unref(node_name);
2770
 
2771
	return CSS_OK;
2772
}
2773
 
2774
static css_error
2775
node_presentational_hint_caption_side(nscss_select_ctx *ctx,
2776
					  dom_node *node,
2777
					  css_hint *hint)
2778
{
2779
	dom_exception err;
2780
	dom_string *node_name = NULL;
2781
	dom_string *align = NULL;
2782
 
2783
	err = dom_node_get_node_name(node, &node_name);
2784
	if ((err != DOM_NO_ERR) || (node_name == NULL)) {
2785
		return CSS_PROPERTY_NOT_SET;
2786
	}
2787
 
2788
	if (!dom_string_caseless_lwc_isequal(node_name,
2789
			corestring_lwc_caption)) {
2790
		dom_string_unref(node_name);
2791
		return CSS_PROPERTY_NOT_SET;
2792
	}
2793
 
2794
	dom_string_unref(node_name);
2795
 
2796
	err = dom_element_get_attribute(node, corestring_dom_align, &align);
2797
	if ((err != DOM_NO_ERR) || (align == NULL)) {
2798
		return CSS_PROPERTY_NOT_SET;
2799
	}
2800
 
2801
	if (dom_string_caseless_lwc_isequal(align, corestring_lwc_bottom)) {
2802
		hint->status = CSS_CAPTION_SIDE_BOTTOM;
2803
		dom_string_unref(align);
2804
		return CSS_OK;
2805
	}
2806
 
2807
	dom_string_unref(align);
2808
 
2809
	return CSS_PROPERTY_NOT_SET;
2810
}
2811
 
2812
static css_error
2813
node_presentational_hint_background_color(nscss_select_ctx *ctx,
2814
					  dom_node *node,
2815
					  css_hint *hint)
2816
{
2817
	dom_exception err;
2818
	dom_string *bgcolor;
2819
 
2820
	err = dom_element_get_attribute(node,
2821
			corestring_dom_bgcolor, &bgcolor);
2822
	if ((err != DOM_NO_ERR) || (bgcolor == NULL)) {
2823
		return CSS_PROPERTY_NOT_SET;
2824
	}
2825
 
2826
	if (nscss_parse_colour((const char *)dom_string_data(bgcolor),
2827
			       &hint->data.color)) {
2828
		hint->status = CSS_BACKGROUND_COLOR_COLOR;
2829
		dom_string_unref(bgcolor);
2830
		return CSS_OK;
2831
	}
2832
 
2833
	dom_string_unref(bgcolor);
2834
 
2835
	return CSS_PROPERTY_NOT_SET;
2836
}
2837
 
2838
static css_error
2839
node_presentational_hint_background_image(nscss_select_ctx *ctx,
2840
					  dom_node *node,
2841
					  css_hint *hint)
2842
{
2843
	dom_exception err;
2844
	dom_string *atr_val;
2845
	nserror error;
2846
	nsurl *url;
2847
	lwc_string *iurl;
2848
	lwc_error lerror;
2849
 
2850
	err = dom_element_get_attribute(node,
2851
			corestring_dom_background, &atr_val);
2852
	if ((err != DOM_NO_ERR) || (atr_val == NULL)) {
2853
		return CSS_PROPERTY_NOT_SET;
2854
	}
2855
 
2856
	error = nsurl_join(ctx->base_url,
2857
			(const char *)dom_string_data(atr_val), &url);
2858
 
2859
	dom_string_unref(atr_val);
2860
 
2861
	if (error != NSERROR_OK) {
2862
		return CSS_NOMEM;
2863
	}
2864
 
2865
	lerror = lwc_intern_string(nsurl_access(url),
2866
			nsurl_length(url), &iurl);
2867
 
2868
	nsurl_unref(url);
2869
 
2870
	if (lerror == lwc_error_oom) {
2871
		return CSS_NOMEM;
2872
	}
2873
 
2874
	if (lerror == lwc_error_ok) {
2875
		hint->data.string = iurl;
2876
		hint->status = CSS_BACKGROUND_IMAGE_IMAGE;
2877
		return CSS_OK;
2878
	}
2879
 
2880
	return CSS_PROPERTY_NOT_SET;
2881
}
2882
 
2883
/**
2884
 * Callback to retrieve presentational hints for a node
2885
 *
2886
 * \param pw        HTML document
2887
 * \param node      DOM node
2888
 * \param property  CSS property to retrieve
2889
 * \param hint      Pointer to hint object to populate
2890
 * \return CSS_OK               on success,
2891
 *         CSS_PROPERTY_NOT_SET if there is no hint for the requested property,
2892
 *         CSS_NOMEM            on memory exhaustion.
2893
 */
2894
css_error node_presentational_hint(void *pw, void *node,
2895
		uint32_t property, css_hint *hint)
2896
{
2897
 
2898
	switch (property) {
2899
	case CSS_PROP_BACKGROUND_IMAGE:
2900
		return node_presentational_hint_background_image(pw, node, hint);
2901
 
2902
	case CSS_PROP_BACKGROUND_COLOR:
2903
		return node_presentational_hint_background_color(pw, node, hint);
2904
	case CSS_PROP_CAPTION_SIDE:
2905
		return node_presentational_hint_caption_side(pw, node, hint);
2906
 
2907
	case CSS_PROP_COLOR:
2908
		return node_presentational_hint_color(pw, node, hint);
2909
 
2910
	case CSS_PROP_FLOAT:
2911
		return node_presentational_hint_float(pw, node, hint);
2912
 
2913
	case CSS_PROP_FONT_SIZE:
2914
		return node_presentational_hint_font_size(pw, node, hint);
2915
 
2916
	case CSS_PROP_HEIGHT:
2917
		return node_presentational_hint_height(pw, node, hint);
2918
 
2919
	case CSS_PROP_WIDTH:
2920
		return node_presentational_hint_width(pw, node, hint);
2921
 
2922
	case CSS_PROP_BORDER_SPACING:
2923
		return node_presentational_hint_border_spacing(pw, node, hint);
2924
 
2925
	case CSS_PROP_BORDER_TOP_COLOR :
2926
	case CSS_PROP_BORDER_RIGHT_COLOR :
2927
	case CSS_PROP_BORDER_BOTTOM_COLOR :
2928
	case CSS_PROP_BORDER_LEFT_COLOR :
2929
		return node_presentational_hint_border_trbl_color(pw, node, hint);
2930
 
2931
	case CSS_PROP_BORDER_TOP_STYLE :
2932
	case CSS_PROP_BORDER_RIGHT_STYLE :
2933
	case CSS_PROP_BORDER_BOTTOM_STYLE :
2934
	case CSS_PROP_BORDER_LEFT_STYLE :
2935
		return node_presentational_hint_border_trbl_style(pw, node, hint);
2936
 
2937
	case CSS_PROP_BORDER_TOP_WIDTH :
2938
	case CSS_PROP_BORDER_RIGHT_WIDTH :
2939
	case CSS_PROP_BORDER_BOTTOM_WIDTH :
2940
	case CSS_PROP_BORDER_LEFT_WIDTH :
2941
		return node_presentational_hint_border_trbl_width(pw, node, hint);
2942
 
2943
	case CSS_PROP_MARGIN_TOP :
2944
	case CSS_PROP_MARGIN_BOTTOM :
2945
		return node_presentational_hint_margin_tb(pw, node, hint);
2946
 
2947
	case CSS_PROP_MARGIN_RIGHT:
2948
	case CSS_PROP_MARGIN_LEFT:
2949
		return node_presentational_hint_margin_rl(pw, node, hint, property);
2950
 
2951
	case CSS_PROP_PADDING_TOP:
2952
	case CSS_PROP_PADDING_RIGHT :
2953
	case CSS_PROP_PADDING_BOTTOM :
2954
	case CSS_PROP_PADDING_LEFT:
2955
		return node_presentational_hint_padding_trbl(pw, node, hint);
2956
 
2957
	case CSS_PROP_TEXT_ALIGN:
2958
		return node_presentational_hint_text_align(pw, node, hint);
2959
 
2960
	case CSS_PROP_VERTICAL_ALIGN:
2961
		return node_presentational_hint_vertical_align(pw, node, hint);
2962
	}
2963
 
2964
	return CSS_PROPERTY_NOT_SET;
2965
}
2966
 
2967
/**
2968
 * Callback to retrieve the User-Agent defaults for a CSS property.
2969
 *
2970
 * \param pw        HTML document
2971
 * \param property  Property to retrieve defaults for
2972
 * \param hint      Pointer to hint object to populate
2973
 * \return CSS_OK       on success,
2974
 *         CSS_INVALID  if the property should not have a user-agent default.
2975
 */
2976
css_error ua_default_for_property(void *pw, uint32_t property, css_hint *hint)
2977
{
2978
	if (property == CSS_PROP_COLOR) {
2979
		hint->data.color = 0xff000000;
2980
		hint->status = CSS_COLOR_COLOR;
2981
	} else if (property == CSS_PROP_FONT_FAMILY) {
2982
		hint->data.strings = NULL;
2983
		switch (nsoption_int(font_default)) {
2984
		case PLOT_FONT_FAMILY_SANS_SERIF:
2985
			hint->status = CSS_FONT_FAMILY_SANS_SERIF;
2986
			break;
2987
		case PLOT_FONT_FAMILY_SERIF:
2988
			hint->status = CSS_FONT_FAMILY_SERIF;
2989
			break;
2990
		case PLOT_FONT_FAMILY_MONOSPACE:
2991
			hint->status = CSS_FONT_FAMILY_MONOSPACE;
2992
			break;
2993
		case PLOT_FONT_FAMILY_CURSIVE:
2994
			hint->status = CSS_FONT_FAMILY_CURSIVE;
2995
			break;
2996
		case PLOT_FONT_FAMILY_FANTASY:
2997
			hint->status = CSS_FONT_FAMILY_FANTASY;
2998
			break;
2999
		}
3000
	} else if (property == CSS_PROP_QUOTES) {
3001
		/** \todo Not exactly useful :) */
3002
		hint->data.strings = NULL;
3003
		hint->status = CSS_QUOTES_NONE;
3004
	} else if (property == CSS_PROP_VOICE_FAMILY) {
3005
		/** \todo Fix this when we have voice-family done */
3006
		hint->data.strings = NULL;
3007
		hint->status = 0;
3008
	} else {
3009
		return CSS_INVALID;
3010
	}
3011
 
3012
	return CSS_OK;
3013
}
3014
 
3015
/**
3016
 * Mapping of colour name to CSS color
3017
 */
3018
struct colour_map {
3019
	const char *name;
3020
	css_color color;
3021
};
3022
 
3023
/**
3024
 * Name comparator for named colour matching
3025
 *
3026
 * \param a  Name to match
3027
 * \param b  Colour map entry to consider
3028
 * \return 0   on match,
3029
 *         < 0 if a < b,
3030
 *         > 0 if b > a.
3031
 */
3032
int cmp_colour_name(const void *a, const void *b)
3033
{
3034
	const char *aa = a;
3035
	const struct colour_map *bb = b;
3036
 
3037
	return strcasecmp(aa, bb->name);
3038
}
3039
 
3040
/**
3041
 * Parse a named colour
3042
 *
3043
 * \param name    Name to parse
3044
 * \param result  Pointer to location to receive css_color
3045
 * \return true on success, false on invalid input
3046
 */
3047
bool parse_named_colour(const char *name, css_color *result)
3048
{
3049
	static const struct colour_map named_colours[] = {
3050
		{ "aliceblue",		0xfff0f8ff },
3051
		{ "antiquewhite",	0xfffaebd7 },
3052
		{ "aqua",		0xff00ffff },
3053
		{ "aquamarine",		0xff7fffd4 },
3054
		{ "azure",		0xfff0ffff },
3055
		{ "beige",		0xfff5f5dc },
3056
		{ "bisque",		0xffffe4c4 },
3057
		{ "black",		0xff000000 },
3058
		{ "blanchedalmond",	0xffffebcd },
3059
		{ "blue",		0xff0000ff },
3060
		{ "blueviolet",		0xff8a2be2 },
3061
		{ "brown",		0xffa52a2a },
3062
		{ "burlywood",		0xffdeb887 },
3063
		{ "cadetblue",		0xff5f9ea0 },
3064
		{ "chartreuse",		0xff7fff00 },
3065
		{ "chocolate",		0xffd2691e },
3066
		{ "coral",		0xffff7f50 },
3067
		{ "cornflowerblue",	0xff6495ed },
3068
		{ "cornsilk",		0xfffff8dc },
3069
		{ "crimson",		0xffdc143c },
3070
		{ "cyan",		0xff00ffff },
3071
		{ "darkblue",		0xff00008b },
3072
		{ "darkcyan",		0xff008b8b },
3073
		{ "darkgoldenrod",	0xffb8860b },
3074
		{ "darkgray",		0xffa9a9a9 },
3075
		{ "darkgreen",		0xff006400 },
3076
		{ "darkgrey",		0xffa9a9a9 },
3077
		{ "darkkhaki",		0xffbdb76b },
3078
		{ "darkmagenta",	0xff8b008b },
3079
		{ "darkolivegreen",	0xff556b2f },
3080
		{ "darkorange",		0xffff8c00 },
3081
		{ "darkorchid",		0xff9932cc },
3082
		{ "darkred",		0xff8b0000 },
3083
		{ "darksalmon",		0xffe9967a },
3084
		{ "darkseagreen",	0xff8fbc8f },
3085
		{ "darkslateblue",	0xff483d8b },
3086
		{ "darkslategray",	0xff2f4f4f },
3087
		{ "darkslategrey",	0xff2f4f4f },
3088
		{ "darkturquoise",	0xff00ced1 },
3089
		{ "darkviolet",		0xff9400d3 },
3090
		{ "deeppink",		0xffff1493 },
3091
		{ "deepskyblue",	0xff00bfff },
3092
		{ "dimgray",		0xff696969 },
3093
		{ "dimgrey",		0xff696969 },
3094
		{ "dodgerblue",		0xff1e90ff },
3095
		{ "feldspar",		0xffd19275 },
3096
		{ "firebrick",		0xffb22222 },
3097
		{ "floralwhite",	0xfffffaf0 },
3098
		{ "forestgreen",	0xff228b22 },
3099
		{ "fuchsia",		0xffff00ff },
3100
		{ "gainsboro",		0xffdcdcdc },
3101
		{ "ghostwhite",		0xfff8f8ff },
3102
		{ "gold",		0xffffd700 },
3103
		{ "goldenrod",		0xffdaa520 },
3104
		{ "gray",		0xff808080 },
3105
		{ "green",		0xff008000 },
3106
		{ "greenyellow",	0xffadff2f },
3107
		{ "grey",		0xff808080 },
3108
		{ "honeydew",		0xfff0fff0 },
3109
		{ "hotpink",		0xffff69b4 },
3110
		{ "indianred",		0xffcd5c5c },
3111
		{ "indigo",		0xff4b0082 },
3112
		{ "ivory",		0xfffffff0 },
3113
		{ "khaki",		0xfff0e68c },
3114
		{ "lavender",		0xffe6e6fa },
3115
		{ "lavenderblush",	0xfffff0f5 },
3116
		{ "lawngreen",		0xff7cfc00 },
3117
		{ "lemonchiffon",	0xfffffacd },
3118
		{ "lightblue",		0xffadd8e6 },
3119
		{ "lightcoral",		0xfff08080 },
3120
		{ "lightcyan",		0xffe0ffff },
3121
		{ "lightgoldenrodyellow",	0xfffafad2 },
3122
		{ "lightgray",		0xffd3d3d3 },
3123
		{ "lightgreen",		0xff90ee90 },
3124
		{ "lightgrey",		0xffd3d3d3 },
3125
		{ "lightpink",		0xffffb6c1 },
3126
		{ "lightsalmon",	0xffffa07a },
3127
		{ "lightseagreen",	0xff20b2aa },
3128
		{ "lightskyblue",	0xff87cefa },
3129
		{ "lightslateblue",	0xff8470ff },
3130
		{ "lightslategray",	0xff778899 },
3131
		{ "lightslategrey",	0xff778899 },
3132
		{ "lightsteelblue",	0xffb0c4de },
3133
		{ "lightyellow",	0xffffffe0 },
3134
		{ "lime",		0xff00ff00 },
3135
		{ "limegreen",		0xff32cd32 },
3136
		{ "linen",		0xfffaf0e6 },
3137
		{ "magenta",		0xffff00ff },
3138
		{ "maroon",		0xff800000 },
3139
		{ "mediumaquamarine",	0xff66cdaa },
3140
		{ "mediumblue",		0xff0000cd },
3141
		{ "mediumorchid",	0xffba55d3 },
3142
		{ "mediumpurple",	0xff9370db },
3143
		{ "mediumseagreen",	0xff3cb371 },
3144
		{ "mediumslateblue",	0xff7b68ee },
3145
		{ "mediumspringgreen",	0xff00fa9a },
3146
		{ "mediumturquoise",	0xff48d1cc },
3147
		{ "mediumvioletred",	0xffc71585 },
3148
		{ "midnightblue",	0xff191970 },
3149
		{ "mintcream",		0xfff5fffa },
3150
		{ "mistyrose",		0xffffe4e1 },
3151
		{ "moccasin",		0xffffe4b5 },
3152
		{ "navajowhite",	0xffffdead },
3153
		{ "navy",		0xff000080 },
3154
		{ "oldlace",		0xfffdf5e6 },
3155
		{ "olive",		0xff808000 },
3156
		{ "olivedrab",		0xff6b8e23 },
3157
		{ "orange",		0xffffa500 },
3158
		{ "orangered",		0xffff4500 },
3159
		{ "orchid",		0xffda70d6 },
3160
		{ "palegoldenrod",	0xffeee8aa },
3161
		{ "palegreen",		0xff98fb98 },
3162
		{ "paleturquoise",	0xffafeeee },
3163
		{ "palevioletred",	0xffdb7093 },
3164
		{ "papayawhip",		0xffffefd5 },
3165
		{ "peachpuff",		0xffffdab9 },
3166
		{ "peru",		0xffcd853f },
3167
		{ "pink",		0xffffc0cb },
3168
		{ "plum",		0xffdda0dd },
3169
		{ "powderblue",		0xffb0e0e6 },
3170
		{ "purple",		0xff800080 },
3171
		{ "red",		0xffff0000 },
3172
		{ "rosybrown",		0xffbc8f8f },
3173
		{ "royalblue",		0xff4169e1 },
3174
		{ "saddlebrown",	0xff8b4513 },
3175
		{ "salmon",		0xfffa8072 },
3176
		{ "sandybrown",		0xfff4a460 },
3177
		{ "seagreen",		0xff2e8b57 },
3178
		{ "seashell",		0xfffff5ee },
3179
		{ "sienna",		0xffa0522d },
3180
		{ "silver",		0xffc0c0c0 },
3181
		{ "skyblue",		0xff87ceeb },
3182
		{ "slateblue",		0xff6a5acd },
3183
		{ "slategray",		0xff708090 },
3184
		{ "slategrey",		0xff708090 },
3185
		{ "snow",		0xfffffafa },
3186
		{ "springgreen",	0xff00ff7f },
3187
		{ "steelblue",		0xff4682b4 },
3188
		{ "tan",		0xffd2b48c },
3189
		{ "teal",		0xff008080 },
3190
		{ "thistle",		0xffd8bfd8 },
3191
		{ "tomato",		0xffff6347 },
3192
		{ "turquoise",		0xff40e0d0 },
3193
		{ "violet",		0xffee82ee },
3194
		{ "violetred",		0xffd02090 },
3195
		{ "wheat",		0xfff5deb3 },
3196
		{ "white",		0xffffffff },
3197
		{ "whitesmoke",		0xfff5f5f5 },
3198
		{ "yellow",		0xffffff00 },
3199
		{ "yellowgreen",	0xff9acd32 }
3200
	};
3201
	const struct colour_map *entry;
3202
 
3203
	entry = bsearch(name, named_colours,
3204
			sizeof(named_colours) / sizeof(named_colours[0]),
3205
			sizeof(named_colours[0]),
3206
			cmp_colour_name);
3207
 
3208
	if (entry != NULL)
3209
		*result = entry->color;
3210
 
3211
	return entry != NULL;
3212
}
3213
 
3214
/**
3215
 * Parse a dimension string
3216
 *
3217
 * \param data    Data to parse (NUL-terminated)
3218
 * \param strict  Whether to enforce strict parsing rules
3219
 * \param length  Pointer to location to receive dimension's length
3220
 * \param unit    Pointer to location to receive dimension's unit
3221
 * \return true on success, false on invalid input
3222
 */
3223
bool parse_dimension(const char *data, bool strict, css_fixed *length,
3224
		css_unit *unit)
3225
{
3226
	size_t len;
3227
	size_t read;
3228
	css_fixed value;
3229
 
3230
	len = strlen(data);
3231
 
3232
	if (parse_number(data, false, true, &value, &read) == false)
3233
		return false;
3234
 
3235
	if (strict && value < INTTOFIX(1))
3236
		return false;
3237
 
3238
	*length = value;
3239
 
3240
	if (len > read && data[read] == '%')
3241
		*unit = CSS_UNIT_PCT;
3242
	else
3243
		*unit = CSS_UNIT_PX;
3244
 
3245
	return true;
3246
}
3247
 
3248
/**
3249
 * Parse a number string
3250
 *
3251
 * \param data  Data to parse (NUL-terminated)
3252
 * \param maybe_negative  Negative numbers permitted
3253
 * \param real            Floating point numbers permitted
3254
 * \param value           Pointer to location to receive numeric value
3255
 * \param consumed        Pointer to location to receive number of input
3256
 *                        bytes consumed
3257
 * \return true on success, false on invalid input
3258
 */
3259
bool parse_number(const char *data, bool maybe_negative, bool real,
3260
		css_fixed *value, size_t *consumed)
3261
{
3262
	size_t len;
3263
	const uint8_t *ptr;
3264
	int32_t intpart = 0;
3265
	int32_t fracpart = 0;
3266
	int32_t pwr = 1;
3267
	int sign = 1;
3268
 
3269
	*consumed = 0;
3270
 
3271
	len = strlen(data);
3272
	ptr = (const uint8_t *) data;
3273
 
3274
	if (len == 0)
3275
		return false;
3276
 
3277
	/* Skip leading whitespace */
3278
	while (len > 0 && isWhitespace(ptr[0])) {
3279
		len--;
3280
		ptr++;
3281
	}
3282
 
3283
	if (len == 0)
3284
		return false;
3285
 
3286
	/* Extract sign, if any */
3287
	if (ptr[0] == '+') {
3288
		len--;
3289
		ptr++;
3290
	} else if (ptr[0] == '-' && maybe_negative) {
3291
		sign = -1;
3292
		len--;
3293
		ptr++;
3294
	}
3295
 
3296
	if (len == 0)
3297
		return false;
3298
 
3299
	/* Must have a digit [0,9] */
3300
	if ('0' > ptr[0] || ptr[0] > '9')
3301
		return false;
3302
 
3303
	/* Now extract intpart, assuming base 10 */
3304
	while (len > 0) {
3305
		/* Stop on first non-digit */
3306
		if (ptr[0] < '0' || '9' < ptr[0])
3307
			break;
3308
 
3309
		/* Prevent overflow of 'intpart'; proper clamping below */
3310
		if (intpart < (1 << 22)) {
3311
			intpart *= 10;
3312
			intpart += ptr[0] - '0';
3313
		}
3314
		ptr++;
3315
		len--;
3316
	}
3317
 
3318
	/* And fracpart, again, assuming base 10 */
3319
	if (real && len > 1 && ptr[0] == '.' &&
3320
			('0' <= ptr[1] && ptr[1] <= '9')) {
3321
		ptr++;
3322
		len--;
3323
 
3324
		while (len > 0) {
3325
			if (ptr[0] < '0' || '9' < ptr[0])
3326
				break;
3327
 
3328
			if (pwr < 1000000) {
3329
				pwr *= 10;
3330
				fracpart *= 10;
3331
				fracpart += ptr[0] - '0';
3332
			}
3333
			ptr++;
3334
			len--;
3335
		}
3336
 
3337
		fracpart = ((1 << 10) * fracpart + pwr/2) / pwr;
3338
		if (fracpart >= (1 << 10)) {
3339
			intpart++;
3340
			fracpart &= (1 << 10) - 1;
3341
		}
3342
	}
3343
 
3344
	if (sign > 0) {
3345
		/* If the result is larger than we can represent,
3346
		 * then clamp to the maximum value we can store. */
3347
		if (intpart >= (1 << 21)) {
3348
			intpart = (1 << 21) - 1;
3349
			fracpart = (1 << 10) - 1;
3350
		}
3351
	} else {
3352
		/* If the negated result is smaller than we can represent
3353
		 * then clamp to the minimum value we can store. */
3354
		if (intpart >= (1 << 21)) {
3355
			intpart = -(1 << 21);
3356
			fracpart = 0;
3357
		} else {
3358
			intpart = -intpart;
3359
			if (fracpart) {
3360
				fracpart = (1 << 10) - fracpart;
3361
				intpart--;
3362
			}
3363
		}
3364
	}
3365
 
3366
	*value = (intpart << 10) | fracpart;
3367
 
3368
	*consumed = ptr - (const uint8_t *) data;
3369
 
3370
	return true;
3371
}
3372
 
3373
/**
3374
 * Parse a font \@size attribute
3375
 *
3376
 * \param size  Data to parse (NUL-terminated)
3377
 * \param val   Pointer to location to receive enum value
3378
 * \param len   Pointer to location to receive length
3379
 * \param unit  Pointer to location to receive unit
3380
 * \return True on success, false on failure
3381
 */
3382
bool parse_font_size(const char *size, uint8_t *val,
3383
		css_fixed *len, css_unit *unit)
3384
{
3385
	static const uint8_t size_map[] = {
3386
		CSS_FONT_SIZE_XX_SMALL,
3387
		CSS_FONT_SIZE_SMALL,
3388
		CSS_FONT_SIZE_MEDIUM,
3389
		CSS_FONT_SIZE_LARGE,
3390
		CSS_FONT_SIZE_X_LARGE,
3391
		CSS_FONT_SIZE_XX_LARGE,
3392
		CSS_FONT_SIZE_DIMENSION	/* xxx-large (see below) */
3393
	};
3394
 
3395
	const char *p = size;
3396
	char mode;
3397
	int value = 0;
3398
 
3399
	/* Skip whitespace */
3400
	while (*p != '\0' && isWhitespace(*p))
3401
		p++;
3402
 
3403
	mode = *p;
3404
 
3405
	/* Skip +/- */
3406
	if (mode == '+' || mode == '-')
3407
		p++;
3408
 
3409
	/* Need at least one digit */
3410
	if (*p < '0' || *p > '9') {
3411
		return false;
3412
	}
3413
 
3414
	/* Consume digits, computing value */
3415
	while ('0' <= *p && *p <= '9') {
3416
		value = value * 10 + (*p - '0');
3417
		p++;
3418
	}
3419
 
3420
	/* Resolve relative sizes */
3421
	if (mode == '+')
3422
		value += 3;
3423
	else if (mode == '-')
3424
		value = 3 - value;
3425
 
3426
	/* Clamp to range [1,7] */
3427
	if (value < 1)
3428
		value = 1;
3429
	else if (value > 7)
3430
		value = 7;
3431
 
3432
	if (value == 7) {
3433
		/* Manufacture xxx-large */
3434
	  *len = FDIV(FMUL(INTTOFIX(3), INTTOFIX(nsoption_int(font_size))),
3435
				F_10);
3436
	} else {
3437
		/* Len is irrelevant */
3438
		*len = 0;
3439
	}
3440
 
3441
	*unit = CSS_UNIT_PT;
3442
	*val = size_map[value - 1];
3443
 
3444
	return true;
3445
}
3446
 
3447
/******************************************************************************
3448
 * Utility functions                                                          *
3449
 ******************************************************************************/
3450
 
3451
/**
3452
 * Determine if a given character is whitespace
3453
 *
3454
 * \param c  Character to consider
3455
 * \return true if character is whitespace, false otherwise
3456
 */
3457
bool isWhitespace(char c)
3458
{
3459
	return c == ' ' || c == '\t' || c == '\f' || c == '\r' || c == '\n';
3460
}
3461
 
3462
/**
3463
 * Determine if a given character is a valid hex digit
3464
 *
3465
 * \param c  Character to consider
3466
 * \return true if character is a valid hex digit, false otherwise
3467
 */
3468
bool isHex(char c)
3469
{
3470
	return ('0' <= c && c <= '9') ||
3471
			('A' <= (c & ~0x20) && (c & ~0x20) <= 'F');
3472
}
3473
 
3474
/**
3475
 * Convert a character representing a hex digit to the corresponding hex value
3476
 *
3477
 * \param c  Character to convert
3478
 * \return Hex value represented by character
3479
 *
3480
 * \note This function assumes an ASCII-compatible character set
3481
 */
3482
uint8_t charToHex(char c)
3483
{
3484
	/* 0-9 */
3485
	c -= '0';
3486
 
3487
	/* A-F */
3488
	if (c > 9)
3489
		c -= 'A' - '9' - 1;
3490
 
3491
	/* a-f */
3492
	if (c > 15)
3493
		c -= 'a' - 'A';
3494
 
3495
	return c;
3496
}
3497