462 lines
12 KiB
C
462 lines
12 KiB
C
/*
|
|
* This file is part of NetSurf, http://netsurf.sourceforge.net/
|
|
* Licensed under the GNU General Public License,
|
|
* http://www.opensource.org/licenses/gpl-license
|
|
* Copyright 2005 Richard Wilson <info@tinct.net>
|
|
*/
|
|
|
|
/** \file
|
|
* HTML lists (implementation).
|
|
*/
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "netsurf/css/css.h"
|
|
#include "netsurf/render/list.h"
|
|
#include "netsurf/utils/log.h"
|
|
|
|
|
|
struct list_counter {
|
|
char *name; /** Counter name */
|
|
struct list_counter_state *first; /** First counter state */
|
|
struct list_counter_state *state; /** Current counter state */
|
|
struct list_counter *next; /** Next counter */
|
|
};
|
|
|
|
struct list_counter_state {
|
|
int count; /** Current count */
|
|
struct list_counter_state *parent; /** Parent counter, or NULL */
|
|
struct list_counter_state *next; /** Next counter, or NULL */
|
|
};
|
|
|
|
static struct list_counter *list_counter_pool = NULL;
|
|
static char list_counter_workspace[16];
|
|
|
|
static const char *list_counter_roman[] = { "I", "IV", "V", "IX",
|
|
"X", "XL", "L", "XC",
|
|
"C", "CD", "D", "CM",
|
|
"M"};
|
|
static const int list_counter_decimal[] = { 1, 4, 5, 9,
|
|
10, 40, 50, 90,
|
|
100, 400, 500, 900,
|
|
1000};
|
|
#define ROMAN_DECIMAL_CONVERSIONS (sizeof(list_counter_decimal) \
|
|
/ sizeof(list_counter_decimal[0]))
|
|
|
|
|
|
static struct list_counter *render_list_find_counter(char *name);
|
|
static char *render_list_encode_counter(struct list_counter_state *state,
|
|
css_list_style_type style);
|
|
static char *render_list_encode_roman(int value);
|
|
|
|
/*
|
|
static void render_list_counter_output(char *name);
|
|
*/
|
|
|
|
/**
|
|
* Finds a counter from the current pool, or adds a new one.
|
|
*
|
|
* \param name the name of the counter to find
|
|
* \return the counter, or NULL if it couldn't be found/created.
|
|
*/
|
|
static struct list_counter *render_list_find_counter(char *name) {
|
|
struct list_counter *counter;
|
|
|
|
assert(name);
|
|
/* find a current counter */
|
|
for (counter = list_counter_pool; counter; counter = counter->next)
|
|
if (!strcasecmp(name, counter->name))
|
|
return counter;
|
|
|
|
/* create a new counter */
|
|
counter = calloc(1, sizeof(struct list_counter));
|
|
if (!counter) {
|
|
LOG(("No memory for calloc()"));
|
|
return NULL;
|
|
}
|
|
counter->name = malloc(strlen(name) + 1);
|
|
if (!counter->name) {
|
|
LOG(("No memory for malloc()"));
|
|
return NULL;
|
|
}
|
|
strcpy(counter->name, name);
|
|
counter->next = list_counter_pool;
|
|
list_counter_pool = counter;
|
|
return counter;
|
|
}
|
|
|
|
|
|
/**
|
|
* Removes all counters from the current pool.
|
|
*/
|
|
void render_list_destroy_counters(void) {
|
|
struct list_counter *counter = list_counter_pool;
|
|
struct list_counter *next_counter;
|
|
struct list_counter_state *state;
|
|
struct list_counter_state *next_state;
|
|
|
|
while (counter) {
|
|
next_counter = counter->next;
|
|
free(counter->name);
|
|
state = counter->first;
|
|
free(counter);
|
|
counter = next_counter;
|
|
while (state) {
|
|
next_state = state->next;
|
|
free(state);
|
|
state = next_state;
|
|
}
|
|
}
|
|
list_counter_pool = NULL;
|
|
}
|
|
|
|
|
|
/**
|
|
* Resets a counter in accordance with counter-reset (CSS 2.1/12.4).
|
|
*
|
|
* \param name the name of the counter to reset
|
|
* \param value the value to reset the counter to
|
|
* \return true on success, false on failure.
|
|
*/
|
|
bool render_list_counter_reset(char *name, int value) {
|
|
struct list_counter *counter;
|
|
struct list_counter_state *state;
|
|
struct list_counter_state *link;
|
|
|
|
assert(name);
|
|
counter = render_list_find_counter(name);
|
|
if (!counter)
|
|
return false;
|
|
state = calloc(1, sizeof(struct list_counter_state));
|
|
if (!state) {
|
|
LOG(("No memory for calloc()"));
|
|
return false;
|
|
}
|
|
state->count = value;
|
|
state->parent = counter->state;
|
|
counter->state = state;
|
|
if (!counter->first) {
|
|
counter->first = state;
|
|
} else {
|
|
for (link = counter->first; link->next; link = link->next);
|
|
link->next = state;
|
|
}
|
|
/* render_list_counter_output(name);
|
|
*/ return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Increments a counter in accordance with counter-increment (CSS 2.1/12.4).
|
|
*
|
|
* \param name the name of the counter to reset
|
|
* \param value the value to increment the counter by
|
|
* \return true on success, false on failure.
|
|
*/
|
|
bool render_list_counter_increment(char *name, int value) {
|
|
struct list_counter *counter;
|
|
|
|
assert(name);
|
|
counter = render_list_find_counter(name);
|
|
if (!counter)
|
|
return false;
|
|
/* if no counter-reset used, it is assumed the counter has been reset
|
|
* to 0 by the root element. */
|
|
if (!counter->state) {
|
|
if (counter->first) {
|
|
counter->state = counter->first;
|
|
counter->state->count = 0;
|
|
} else {
|
|
render_list_counter_reset(name, 0);
|
|
}
|
|
}
|
|
if (counter->state)
|
|
counter->state->count += value;
|
|
/* render_list_counter_output(name);
|
|
*/ return (counter->state);
|
|
}
|
|
|
|
|
|
/**
|
|
* Ends the scope of a counter.
|
|
*
|
|
* \param name the name of the counter to end the scope for
|
|
* \return true on success, false on failure.
|
|
*/
|
|
bool render_list_counter_end_scope(char *name) {
|
|
struct list_counter *counter;
|
|
|
|
assert(name);
|
|
counter = render_list_find_counter(name);
|
|
if ((!counter) || (!counter->state))
|
|
return false;
|
|
counter->state = counter->state->parent;
|
|
/* render_list_counter_output(name);
|
|
*/ return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns a textual representation of a counter for counter() or counters()
|
|
* (CSS 2.1/12.2).
|
|
*
|
|
* \param css_counter the counter to convert
|
|
* \return a textual representation of the counter, or NULL on failure
|
|
*/
|
|
char *render_list_counter(struct css_counter *css_counter) {
|
|
struct list_counter *counter;
|
|
struct list_counter_state *state;
|
|
char *compound = NULL;
|
|
char *merge, *extend;
|
|
|
|
assert(css_counter);
|
|
counter = render_list_find_counter(css_counter->name);
|
|
if (!counter) {
|
|
LOG(("Failed to find/create counter for conversion"));
|
|
return NULL;
|
|
}
|
|
|
|
/* handle counter() first */
|
|
if (!css_counter->separator)
|
|
return render_list_encode_counter(counter->state,
|
|
css_counter->style);
|
|
|
|
/* loop through all states for counters() */
|
|
for (state = counter->first; state; state = state->next) {
|
|
merge = render_list_encode_counter(state,
|
|
css_counter->style);
|
|
if (!merge) {
|
|
free(compound);
|
|
return NULL;
|
|
}
|
|
if (!compound) {
|
|
compound = merge;
|
|
} else {
|
|
extend = realloc(compound, strlen(compound) +
|
|
strlen(merge) + 1);
|
|
if (!extend) {
|
|
LOG(("No memory for realloc()"));
|
|
free(compound);
|
|
free(merge);
|
|
return NULL;
|
|
}
|
|
compound = extend;
|
|
strcat(compound, merge);
|
|
}
|
|
if (state->next) {
|
|
merge = realloc(compound, strlen(compound) +
|
|
strlen(css_counter->separator) + 1);
|
|
if (!merge) {
|
|
LOG(("No memory for realloc()"));
|
|
free(compound);
|
|
return NULL;
|
|
}
|
|
compound = merge;
|
|
strcat(compound, css_counter->separator);
|
|
}
|
|
}
|
|
return compound;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns a textual representation of a counter state in a specified style.
|
|
*
|
|
* \param state the counter state to represent
|
|
* \param style the counter style to use
|
|
* \return a textual representation of the counter state, or NULL on failure
|
|
*/
|
|
static char *render_list_encode_counter(struct list_counter_state *state,
|
|
css_list_style_type style) {
|
|
char *result = NULL;
|
|
int i;
|
|
|
|
/* no counter state means that the counter is currently out of scope */
|
|
if (!state) {
|
|
result = malloc(1);
|
|
if (!result)
|
|
return NULL;
|
|
result[0] = '\0';
|
|
return result;
|
|
}
|
|
|
|
/* perform the relevant encoding to upper case where applicable */
|
|
switch (style) {
|
|
case CSS_LIST_STYLE_TYPE_LOWER_ALPHA:
|
|
case CSS_LIST_STYLE_TYPE_UPPER_ALPHA:
|
|
if (state->count <= 0)
|
|
result = calloc(1, 1);
|
|
else
|
|
result = malloc(2);
|
|
if (!result)
|
|
return NULL;
|
|
if (state->count > 0) {
|
|
result[0] = 'A' + (state->count - 1) % 26;
|
|
result[1] = '\0';
|
|
}
|
|
break;
|
|
case CSS_LIST_STYLE_TYPE_DISC:
|
|
case CSS_LIST_STYLE_TYPE_CIRCLE:
|
|
case CSS_LIST_STYLE_TYPE_SQUARE:
|
|
result = malloc(2);
|
|
if (!result)
|
|
return NULL;
|
|
result[0] = '?';
|
|
result[1] = '\0';
|
|
break;
|
|
case CSS_LIST_STYLE_TYPE_LOWER_ROMAN:
|
|
case CSS_LIST_STYLE_TYPE_UPPER_ROMAN:
|
|
result = render_list_encode_roman(state->count);
|
|
if (!result)
|
|
return NULL;
|
|
break;
|
|
case CSS_LIST_STYLE_TYPE_DECIMAL:
|
|
snprintf(list_counter_workspace,
|
|
sizeof list_counter_workspace,
|
|
"%i", state->count);
|
|
result = malloc(strlen(list_counter_workspace) + 1);
|
|
if (!result)
|
|
return NULL;
|
|
strcpy(result, list_counter_workspace);
|
|
break;
|
|
case CSS_LIST_STYLE_TYPE_NONE:
|
|
result = malloc(1);
|
|
if (!result)
|
|
return NULL;
|
|
result[0] = '\0';
|
|
break;
|
|
case CSS_LIST_STYLE_TYPE_INHERIT:
|
|
case CSS_LIST_STYLE_TYPE_UNKNOWN:
|
|
case CSS_LIST_STYLE_TYPE_NOT_SET:
|
|
assert(0);
|
|
break;
|
|
}
|
|
|
|
/* perform case conversion */
|
|
if ((style == CSS_LIST_STYLE_TYPE_LOWER_ALPHA) ||
|
|
(style == CSS_LIST_STYLE_TYPE_LOWER_ROMAN))
|
|
for (i = 0; result[i]; i++)
|
|
result[i] = tolower(result[i]);
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/**
|
|
* Encodes a value in roman numerals.
|
|
* For values that cannot be represented (ie <=0) an empty string is returned.
|
|
*
|
|
* \param value the value to represent
|
|
* \return a string containing the representation, or NULL on failure
|
|
*/
|
|
static char *render_list_encode_roman(int value) {
|
|
int i, overflow, p = 0;
|
|
char temp[10];
|
|
char *result;
|
|
|
|
/* zero and below is returned as an empty string and not erred as
|
|
* if it is counters() will fail to complete other scopes. */
|
|
if (value <= 0) {
|
|
result = malloc(1);
|
|
if (!result)
|
|
return NULL;
|
|
result[0] = '\0';
|
|
return result;
|
|
}
|
|
|
|
/* we only calculate 1->999 and then add a M for each thousand */
|
|
overflow = value / 1000;
|
|
value = value % 1000;
|
|
|
|
/* work backwards through the array */
|
|
for (i = ROMAN_DECIMAL_CONVERSIONS - 1; i >= 0; i--) {
|
|
while (value >= list_counter_decimal[0]) {
|
|
if (value - list_counter_decimal[i] <
|
|
list_counter_decimal[0] - 1)
|
|
break;
|
|
value -= list_counter_decimal[i];
|
|
temp[p++] = list_counter_roman[i][0];
|
|
if (i & 0x1)
|
|
temp[p++] = list_counter_roman[i][1];
|
|
}
|
|
}
|
|
temp[p] = '\0';
|
|
|
|
/* create a copy for the caller including thousands */
|
|
result = malloc(p + overflow + 1);
|
|
if (!result)
|
|
return NULL;
|
|
for (i = 0; i < overflow; i++)
|
|
result[i] = 'M';
|
|
strcpy(&result[overflow], temp);
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void render_list_test(void) {
|
|
/* example given in CSS2.1/12.4.1 */
|
|
/* render_list_counter_reset("item", 0);
|
|
render_list_counter_increment("item", 1);
|
|
render_list_counter_increment("item", 1);
|
|
render_list_counter_reset("item", 0);
|
|
render_list_counter_increment("item", 1);
|
|
render_list_counter_increment("item", 1);
|
|
render_list_counter_increment("item", 1);
|
|
render_list_counter_reset("item", 0);
|
|
render_list_counter_increment("item", 1);
|
|
render_list_counter_end_scope("item");
|
|
render_list_counter_reset("item", 0);
|
|
render_list_counter_increment("item", 1);
|
|
render_list_counter_end_scope("item");
|
|
render_list_counter_increment("item", 1);
|
|
render_list_counter_end_scope("item");
|
|
render_list_counter_increment("item", 1);
|
|
render_list_counter_increment("item", 1);
|
|
render_list_counter_end_scope("item");
|
|
render_list_counter_reset("item", 0);
|
|
render_list_counter_increment("item", 1);
|
|
render_list_counter_increment("item", 1);
|
|
render_list_counter_end_scope("item");
|
|
*/
|
|
}
|
|
/*
|
|
static void render_list_counter_output(char *name) {
|
|
struct list_counter *counter;
|
|
char *result;
|
|
struct css_counter css_counter;
|
|
|
|
assert(name);
|
|
counter = render_list_find_counter(name);
|
|
if (!counter) {
|
|
fprintf(stderr, "Unable to create counter '%s'\n", name);
|
|
return;
|
|
}
|
|
|
|
css_counter.name = name;
|
|
css_counter.style = CSS_LIST_STYLE_TYPE_LOWER_ALPHA;
|
|
css_counter.separator = NULL;
|
|
result = render_list_counter(&css_counter);
|
|
if (!result) {
|
|
fprintf(stderr, "Failed to output counter('%s')\n", name);
|
|
} else {
|
|
fprintf(stderr, "counter('%s') is '%s'\n", name, result);
|
|
free(result);
|
|
}
|
|
css_counter.separator = ".";
|
|
result = render_list_counter(&css_counter);
|
|
if (!result) {
|
|
fprintf(stderr, "Failed to output counters('%s', '.')\n", name);
|
|
} else {
|
|
fprintf(stderr, "counters('%s', '.') is '%s'\n", name, result);
|
|
free(result);
|
|
}
|
|
}
|
|
*/
|