/* * XML/HTML parser for qemacs. * * Copyright (c) 2000-2002 Fabrice Bellard. * Copyright (c) 2007-2025 Charlie Gordon. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "util.h" #include "charset.h" #include "css.h" // XXX: need fix for this: should use read function with opaque argument struct EditBuffer; char32_t eb_nextc(struct EditBuffer *b, int offset, int *next_ptr); //#define DEBUG #if 0 #ifdef DEBUG #define dprintf(fmt, args...) printf(fmt, ##args) #else #define dprintf(fmt, args...) #define NDEBUG #endif #define ddprintf(fmt, args...) #else #ifdef DEBUG #define dprintf(fmt, ...) printf(fmt, ##__VA_ARGS__) #else #define dprintf(...) #define NDEBUG #endif #define ddprintf(...) #endif #include /* entities definition */ typedef struct { const char *name; int val; } XMLEntity; #define ENTITY(name, value) { #name, value }, static XMLEntity const html_entities[] = { #include "htmlent.h" { NULL, 0 }, }; /* return -1 if not found */ int find_entity(const char *str) { const char *name; const XMLEntity *e; int code; if (str[0] == '#') { if (str[1] == 'x') code = strtol(str + 2, NULL, 16); else code = strtol(str + 1, NULL, 10); if (code <= 0) return -1; return code; } e = html_entities; for (;;) { name = e->name; if (!name) break; if (strequal(str, name)) return e->val; e++; } return -1; } const char *find_entity_str(int code) { const XMLEntity *e; e = html_entities; for (;;) { if (!e->name) break; if (e->val == code) return e->name; e++; } return NULL; } /* parse an entity or a normal char (only ASCII chars are expected) */ static int parse_entity(const char **pp) { const char *p, *p1; char name[16], *q; int ch, ch1; p = *pp; ch = (u8)*p++; if (ch == '&') { p1 = p; q = name; for (;;) { ch1 = (u8)*p; if (ch1 == '\0') break; p++; if (ch1 == ';') break; *q++ = ch1; if (q >= name + sizeof(name) - 1) break; } *q = '\0'; ch1 = find_entity(name); if (ch1 >= 0) { ch = ch1; } else { p = p1; } } *pp = p; return ch; } /***********************************************************/ /* XML parser */ enum XMLParseState { XML_STATE_TEXT, XML_STATE_TAG, XML_STATE_COMMENT, XML_STATE_COMMENT1, XML_STATE_COMMENT2, XML_STATE_PRETAG, XML_STATE_WAIT_EOT, }; #define STRING_BUF_SIZE 4096 //#define STRING_BUF_SIZE 256 /* string buffer optimized for strings lengths <= STRING_BUF_SIZE */ typedef struct StringBuffer { char *buf; int allocated_size; int size; char buf1[STRING_BUF_SIZE]; } StringBuffer; static inline void strbuf_init(StringBuffer *b) { b->buf = b->buf1; b->allocated_size = STRING_BUF_SIZE; b->size = 0; } static inline void strbuf_reset(StringBuffer *b) { if (b->buf != b->buf1) qe_free(&b->buf); strbuf_init(b); } static void strbuf_addch1(StringBuffer *b, char32_t ch) { int size1; char *ptr; size1 = b->allocated_size + STRING_BUF_SIZE; ptr = b->buf; if (b->buf == b->buf1) ptr = NULL; if (qe_realloc_bytes(&ptr, size1)) { if (b->buf == b->buf1) memcpy(ptr, b->buf1, STRING_BUF_SIZE); b->buf = ptr; b->allocated_size = size1; b->size += utf8_encode(b->buf + b->size, ch); } } static inline void strbuf_addch(StringBuffer *b, char32_t ch) { if (b->size + UTF8_CHAR_LEN_MAX < b->allocated_size) { /* fast case */ b->size += utf8_encode(b->buf + b->size, ch); } else { strbuf_addch1(b, ch); } } #if 0 /* offset compression */ typedef struct OffsetBuffer { unsigned int *offsets; int nb_allocated_offsets; int nb_offsets; unsigned int last_offset; } OffsetBuffer; static void offsetbuf_init(OffsetBuffer *b) { b->offsets = NULL; b->nb_allocated_offsets = 0; b->nb_offsets = 0; b->last_offset = -2; /* ensures that a normal offset will be added first without adding more tests */ } static void offsetbuf_reset(OffsetBuffer *b) { qe_free(&b->offsets); offsetbuf_init(b); } /* XXX: return an error if memory fails ? */ static void offsetbuf_add(OffsetBuffer *b, unsigned int offset) { int n; if (offset == (b->last_offset + 1)) { /* just indicate a run of offsets */ b->offsets[b->nb_offsets - 1] |= 0x80000000; } else { if (b->nb_offsets >= b->nb_allocated_offsets) { n = b->nb_allocated_offsets; if (!n) n = 1; n = n * 2; if (!qe_realloc_array(&b->offsets, n)) return; b->nb_allocated_offsets = n; } b->offsets[b->nb_offsets++] = offset; } b->last_offset = offset; } #endif #define LOOKAHEAD_SIZE 16 struct XMLState { CSSBox *root_box; CSSBox *box; int is_html; int html_syntax; int ignore_case; int flags; CSSStyleSheet *style_sheet; /* if non NULL, all style sheets are add to that */ enum XMLParseState state; int line_num; CSSAbortFunc *abort_func; void *abort_opaque; QECharset *charset; int base_font; /* XXX: is it correct ? */ int lookahead_size; char lookahead_buf[2 * LOOKAHEAD_SIZE]; int pretaglen; char pretag[32]; /* current tag in XML_STATE_PRETAG */ StringBuffer str; void *error_opaque; char filename[MAX_FILENAME_SIZE]; CharsetDecodeState charset_state; }; /* start xml parsing */ XMLState *xml_begin(CSSStyleSheet *style_sheet, int flags, CSSAbortFunc *abort_func, void *abort_opaque, void *error_opaque, const char *filename, QECharset *charset) { XMLState *s; s = qe_mallocz(XMLState); if (!s) return NULL; s->flags = flags; s->is_html = flags & XML_HTML; s->html_syntax = flags & XML_HTML_SYNTAX; s->ignore_case = flags & XML_IGNORE_CASE; s->state = XML_STATE_TEXT; s->box = NULL; s->style_sheet = style_sheet; strbuf_init(&s->str); s->abort_func = abort_func; s->abort_opaque = abort_opaque; s->base_font = 3; s->line_num = 1; s->error_opaque = error_opaque; pstrcpy(s->filename, sizeof(s->filename), filename); s->charset = charset; if (charset) { charset_decode_init(&s->charset_state, charset, EOL_UNIX); } return s; } static CSSAttribute *box_new_attr(CSSIdent attr_id, const char *value) { CSSAttribute *attr; int len = strlen(value); attr = qe_malloc_hack(CSSAttribute, len); if (!attr) return NULL; attr->attr = attr_id; attr->next = NULL; memcpy(attr->value, value, len + 1); return attr; } static const char *css_attr_str(CSSBox *box, CSSIdent attr_id) { CSSAttribute *attr; attr = box->attrs; while (attr != NULL) { if (attr->attr == attr_id) return attr->value; attr = attr->next; } return NULL; } static const char *css_attr_strlower(CSSBox *box, CSSIdent attr_id) { static char buf[200]; /* ugly patch to return a string */ const char *value; value = css_attr_str(box, attr_id); if (!value) return NULL; qe_strtolower(buf, sizeof(buf), value); return buf; } static int css_attr_int(CSSBox *box, CSSIdent attr_id, int def_val) { const char *str, *p; int val; str = css_attr_str(box, attr_id); if (!str) return def_val; val = strtol_c(str, &p, 10); /* exclude non numeric inputs (for example percentages) */ if (*p != '\0') return def_val; return val; } /* simplistic HTML table border handling */ static void html_table_borders(CSSBox *box, int border, int padding) { CSSProperty **last_prop; CSSBox *box1; if (box->tag == CSS_ID_td) { last_prop = &box->properties; while (*last_prop != NULL) last_prop = &(*last_prop)->next; if (border >= 1) { css_add_prop_unit(&last_prop, CSS_border_left_width, CSS_UNIT_PIXEL, border); css_add_prop_unit(&last_prop, CSS_border_right_width, CSS_UNIT_PIXEL, border); css_add_prop_unit(&last_prop, CSS_border_top_width, CSS_UNIT_PIXEL, border); css_add_prop_unit(&last_prop, CSS_border_bottom_width, CSS_UNIT_PIXEL, border); css_add_prop_int(&last_prop, CSS_border_left_style, CSS_BORDER_STYLE_RIDGE); css_add_prop_int(&last_prop, CSS_border_right_style, CSS_BORDER_STYLE_RIDGE); css_add_prop_int(&last_prop, CSS_border_top_style, CSS_BORDER_STYLE_RIDGE); css_add_prop_int(&last_prop, CSS_border_bottom_style, CSS_BORDER_STYLE_RIDGE); } if (padding >= 1) { css_add_prop_unit(&last_prop, CSS_padding_left, CSS_UNIT_PIXEL, padding); css_add_prop_unit(&last_prop, CSS_padding_right, CSS_UNIT_PIXEL, padding); css_add_prop_unit(&last_prop, CSS_padding_top, CSS_UNIT_PIXEL, padding); css_add_prop_unit(&last_prop, CSS_padding_bottom, CSS_UNIT_PIXEL, padding); } } if (box->content_type == CSS_CONTENT_TYPE_CHILDS) { for (box1 = box->u.child.first; box1 != NULL; box1 = box1->next) { html_table_borders(box1, border, padding); } } } #define DEFAULT_IMG_WIDTH 32 #define DEFAULT_IMG_HEIGHT 32 static void html_eval_tag(XMLState *s, CSSBox *box) { const char *value; CSSProperty *first_prop, **last_prop; QEColor color; int width, height, val, type; int border, padding; CSSPropertyValue arg; CSSPropertyValue args[2]; first_prop = NULL; last_prop = &first_prop; switch (box->tag) { case CSS_ID_img: parse_img: box->content_type = CSS_CONTENT_TYPE_IMAGE; box->u.image.content_alt = NULL; /* set alt content */ value = css_attr_str(box, CSS_ID_alt); if (!value) { /* if no alt, display the name of the image */ value = css_attr_str(box, CSS_ID_src); if (value) value = get_basename(value); } if (value && value[0] != '\0') { arg.type = CSS_VALUE_STRING; arg.u.str = qe_strdup(value); css_add_prop(&last_prop, CSS_content_alt, &arg); } width = css_attr_int(box, CSS_ID_width, 0); if (width <= 0) width = DEFAULT_IMG_WIDTH; height = css_attr_int(box, CSS_ID_height, 0); if (height <= 0) height = DEFAULT_IMG_HEIGHT; css_add_prop_unit(&last_prop, CSS_width, CSS_UNIT_PIXEL, width); css_add_prop_unit(&last_prop, CSS_height, CSS_UNIT_PIXEL, height); /* border */ val = css_attr_int(box, CSS_ID_border, -1); if (val >= 0) { css_add_prop_unit(&last_prop, CSS_border_left_width, CSS_UNIT_PIXEL, val); css_add_prop_unit(&last_prop, CSS_border_right_width, CSS_UNIT_PIXEL, val); css_add_prop_unit(&last_prop, CSS_border_top_width, CSS_UNIT_PIXEL, val); css_add_prop_unit(&last_prop, CSS_border_bottom_width, CSS_UNIT_PIXEL, val); css_add_prop_int(&last_prop, CSS_border_left_style, CSS_BORDER_STYLE_SOLID); css_add_prop_int(&last_prop, CSS_border_right_style, CSS_BORDER_STYLE_SOLID); css_add_prop_int(&last_prop, CSS_border_top_style, CSS_BORDER_STYLE_SOLID); css_add_prop_int(&last_prop, CSS_border_bottom_style, CSS_BORDER_STYLE_SOLID); } /* margins */ val = css_attr_int(box, CSS_ID_hspace, -1); if (val >= 0) { css_add_prop_unit(&last_prop, CSS_margin_left, CSS_UNIT_PIXEL, val); css_add_prop_unit(&last_prop, CSS_margin_right, CSS_UNIT_PIXEL, val); } val = css_attr_int(box, CSS_ID_vspace, -1); if (val >= 0) { css_add_prop_unit(&last_prop, CSS_margin_top, CSS_UNIT_PIXEL, val); css_add_prop_unit(&last_prop, CSS_margin_bottom, CSS_UNIT_PIXEL, val); } break; case CSS_ID_body: value = css_attr_str(box, CSS_ID_text); if (value && !css_get_color(&color, value)) { css_add_prop_int(&last_prop, CSS_color, color); } /* we handle link by adding a new stylesheet entry */ value = css_attr_str(box, CSS_ID_link); if (value && !css_get_color(&color, value)) { CSSStyleSheetEntry *e; CSSSimpleSelector ss1, *ss = &ss1; CSSProperty **last_prop1; CSSStyleSheetAttributeEntry **plast_attr; /* specific to tag */ memset(ss, 0, sizeof(CSSSimpleSelector)); ss->tag = CSS_ID_a; plast_attr = &ss->attrs; add_attribute(&plast_attr, CSS_ID_href, CSS_ATTR_OP_SET, ""); e = add_style_entry(s->style_sheet, ss, CSS_MEDIA_ALL); /* add color property */ last_prop1 = &e->props; css_add_prop_int(&last_prop1, CSS_color, color); } break; case CSS_ID_font: case CSS_ID_basefont: /* size */ value = css_attr_str(box, CSS_ID_size); if (value) { val = strtol(value, NULL, 10); if (value[0] == '+' || value[0] == '-') { /* relative size */ val += s->base_font; } if (val < 1) val = 1; else if (val > 7) val = 7; if (box->tag == CSS_ID_basefont) s->base_font = val; /* XXX: incorrect for basefont */ css_add_prop_unit(&last_prop, CSS_font_size, CSS_UNIT_IN, get_font_size(val - 1)); } /* color */ value = css_attr_str(box, CSS_ID_color); if (value && !css_get_color(&color, value)) { css_add_prop_int(&last_prop, CSS_color, color); } break; case CSS_ID_br: value = css_attr_strlower(box, CSS_ID_clear); if (value) { val = css_get_enum(value, "none,left,right,all"); if (val >= 0) { css_add_prop_int(&last_prop, CSS_clear, val + CSS_CLEAR_NONE); } } break; case CSS_ID_table: val = css_attr_int(box, CSS_ID_width, -1); if (val >= 0) { css_add_prop_unit(&last_prop, CSS_width, CSS_UNIT_PIXEL, val); } border = css_attr_int(box, CSS_ID_border, -1); if (border >= 0) { css_add_prop_unit(&last_prop, CSS_border_left_width, CSS_UNIT_PIXEL, border); css_add_prop_unit(&last_prop, CSS_border_right_width, CSS_UNIT_PIXEL, border); css_add_prop_unit(&last_prop, CSS_border_top_width, CSS_UNIT_PIXEL, border); css_add_prop_unit(&last_prop, CSS_border_bottom_width, CSS_UNIT_PIXEL, border); css_add_prop_int(&last_prop, CSS_border_left_style, CSS_BORDER_STYLE_GROOVE); css_add_prop_int(&last_prop, CSS_border_right_style, CSS_BORDER_STYLE_GROOVE); css_add_prop_int(&last_prop, CSS_border_top_style, CSS_BORDER_STYLE_GROOVE); css_add_prop_int(&last_prop, CSS_border_bottom_style, CSS_BORDER_STYLE_GROOVE); /* cell have a border of 1 pixel width */ if (border > 1) border = 1; } val = css_attr_int(box, CSS_ID_cellspacing, -1); if (val >= 0) { css_add_prop_unit(&last_prop, CSS_border_spacing_horizontal, CSS_UNIT_PIXEL, val); css_add_prop_unit(&last_prop, CSS_border_spacing_vertical, CSS_UNIT_PIXEL, val); } padding = css_attr_int(box, CSS_ID_cellpadding, -1); /* apply border styles to each cell (cannot be done exactly by CSS) */ if (border >= 1 || padding >= 1) html_table_borders(box, border, padding); break; case CSS_ID_col: case CSS_ID_colgroup: val = css_attr_int(box, CSS_ID_width, -1); if (val >= 0) { css_add_prop_unit(&last_prop, CSS_width, CSS_UNIT_PIXEL, val); } break; case CSS_ID_td: val = css_attr_int(box, CSS_ID_width, -1); if (val >= 0) { css_add_prop_unit(&last_prop, CSS_width, CSS_UNIT_PIXEL, val); } val = css_attr_int(box, CSS_ID_height, -1); if (val >= 0) { css_add_prop_unit(&last_prop, CSS_height, CSS_UNIT_PIXEL, val); } break; case CSS_ID_ol: case CSS_ID_li: /* NOTE: case is important */ /* XXX: currently cannot propagate for LI tag */ value = css_attr_str(box, CSS_ID_type); if (value) { val = css_get_enum(value, "1,a,A,i,I"); if (val >= 0) { val += CSS_LIST_STYLE_TYPE_DECIMAL; css_add_prop_int(&last_prop, CSS_list_style_type, val); } } /* XXX: add value, but needs a css extension */ if (box->tag == CSS_ID_ol) val = CSS_ID_start; else val = CSS_ID_value; /* NOTE: only works with digits */ val = css_attr_int(box, val, 0); if (val > 0) { args[0].type = CSS_VALUE_IDENT; args[0].u.val = CSS_ID_list_item; args[1].type = CSS_VALUE_INTEGER; args[1].u.val = val - 1; /* currently needs minus 1 */ css_add_prop_values(&last_prop, CSS_counter_reset, 2, args); } break; /* controls */ case CSS_ID_button: type = CSS_ID_submit; value = css_attr_strlower(box, CSS_ID_type); if (value) type = css_new_ident(value); if (type != CSS_ID_button && type != CSS_ID_reset) type = CSS_ID_submit; goto parse_input; case CSS_ID_input: type = CSS_ID_text; value = css_attr_strlower(box, CSS_ID_type); if (value) { type = css_new_ident(value); } else { CSSAttribute *attr; /* NOTE: we add an attribute for css rules */ attr = box_new_attr(CSS_ID_type, "text"); if (attr) { attr->next = box->attrs; box->attrs = attr; } } parse_input: if (type == CSS_ID_image) goto parse_img; if (type == CSS_ID_button || type == CSS_ID_reset || type == CSS_ID_submit || type == CSS_ID_text || type == CSS_ID_password || type == CSS_ID_file) { /* put text inside the box (XXX: use attr() in content attribute ? */ value = css_attr_str(box, CSS_ID_value); if (value) { css_set_text_string(box, value); } } /* size */ if (type == CSS_ID_text || type == CSS_ID_password) { val = css_attr_int(box, CSS_ID_size, 10); css_add_prop_unit(&last_prop, CSS_width, CSS_UNIT_EM, val << CSS_LENGTH_FRAC_BITS); } break; case CSS_ID_textarea: val = css_attr_int(box, CSS_ID_cols, 10); if (val < 1) val = 1; css_add_prop_unit(&last_prop, CSS_width, CSS_UNIT_EM, val << CSS_LENGTH_FRAC_BITS); val = css_attr_int(box, CSS_ID_rows, 1); if (val < 1) val = 1; css_add_prop_unit(&last_prop, CSS_height, CSS_UNIT_EM, val << CSS_LENGTH_FRAC_BITS); break; case CSS_ID_select: val = css_attr_int(box, CSS_ID_size, 1); if (val < 1) val = 1; css_add_prop_unit(&last_prop, CSS_height, CSS_UNIT_EM, val << CSS_LENGTH_FRAC_BITS); break; default: break; } /* generic attributes */ value = css_attr_str(box, CSS_ID_bgcolor); if (value && !css_get_color(&color, value)) { css_add_prop_int(&last_prop, CSS_background_color, color); } value = css_attr_strlower(box, CSS_ID_align); if (value) { switch (box->tag) { case CSS_ID_caption: /* use caption-side property for captions */ val = css_get_enum(value, "top,bottom,left,right"); if (val >= 0) { css_add_prop_int(&last_prop, CSS_caption_side, val); } break; case CSS_ID_img: /* floating images */ val = css_get_enum(value, "left,right"); if (val >= 0) { css_add_prop_int(&last_prop, CSS_float, val + CSS_FLOAT_LEFT); } break; case CSS_ID_table: val = css_get_enum(value, "left,right,center"); if (val == CSS_TEXT_ALIGN_LEFT || val == CSS_TEXT_ALIGN_RIGHT) { css_add_prop_int(&last_prop, CSS_float, val + CSS_FLOAT_LEFT); } else if (val == CSS_TEXT_ALIGN_CENTER) { css_add_prop_int(&last_prop, CSS_margin_left, CSS_AUTO); css_add_prop_int(&last_prop, CSS_margin_right, CSS_AUTO); } break; default: val = css_get_enum(value, "left,right,center"); if (val >= 0) { css_add_prop_int(&last_prop, CSS_text_align, val); } break; } } value = css_attr_strlower(box, CSS_ID_valign); if (value) { val = css_get_enum(value, "baseline,,,top,,middle,bottom"); if (val >= 0) { css_add_prop_int(&last_prop, CSS_vertical_align, val); } } val = css_attr_int(box, CSS_ID_colspan, 1); if (val > 1) { css_add_prop_unit(&last_prop, CSS_column_span, CSS_VALUE_INTEGER, val); } val = css_attr_int(box, CSS_ID_rowspan, 1); if (val > 1) { css_add_prop_unit(&last_prop, CSS_row_span, CSS_VALUE_INTEGER, val); } value = css_attr_str(box, CSS_ID_style); if (value) { CSSParseState b1, *b = &b1; b->error_opaque = s->error_opaque; b->ptr = NULL; b->filename = s->filename; b->line_num = s->line_num; /* XXX: slightly incorrect */ b->ignore_case = s->ignore_case; *last_prop = css_parse_properties(b, value); } box->properties = first_prop; } static void xml_error(XMLState *s, const char *fmt, ...) qe__attr_printf(2,3); static void xml_error(XMLState *s, const char *fmt, ...) { char buf[1024]; va_list ap; va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); css_error(s->error_opaque, s->filename, s->line_num, buf); } /* XXX: avoid using strings in tag_closed and generalize */ typedef struct { CSSIdent tag; /* tag to handle */ const char *tag_closed; /* all the following tags can be closed automatically */ } HTMLClosedTags; static const HTMLClosedTags html_closed_tags[] = { { CSS_ID_li, "li,b,i,em,s,u,strike,strong,a" }, { CSS_ID_td, "td,th,b,i,em,s,u,strike,strong,a,li" }, { CSS_ID_th, "td,th,b,i,em,s,u,strike,strong,a,li" }, { CSS_ID_tr, "tr,td,th,b,i,em,s,u,strike,strong,a,li" }, { CSS_ID_dt, "dd,b,i,em,s,u,strike,strong,a" }, { CSS_ID_dd, "dt,b,i,em,s,u,strike,strong,a" }, { CSS_ID_b, "i,em,s,u,strike,strong" }, { CSS_ID_table, "font" }, { CSS_ID_NIL, NULL }, }; static int parse_tag(XMLState *s, const char *buf) { char tag[256], *q, len, eot; char attr_name[256]; char value[2048]; const char *p; CSSIdent css_tag; CSSBox *box, *box1; CSSAttribute *first_attr, **pattr, *attr; p = buf; /* ignore XML commands */ if (p[0] == '!' || p[0] == '?') return XML_STATE_TEXT; /* end of tag check */ eot = 0; if (*p == '/') { p++; eot = 1; } /* parse the tag name */ get_str(&p, tag, sizeof(tag), "/"); if (tag[0] == '\0') { /* special closing tag */ if (eot) { css_tag = CSS_ID_NIL; goto end_of_tag; } else { xml_error(s, "invalid null tag"); return XML_STATE_TEXT; } } if (s->ignore_case) qe_strtolower(tag, sizeof(tag), tag); css_tag = css_new_ident(tag); /* XXX: should test html_syntax, but need more patches */ if (s->is_html && (css_tag == CSS_ID_style || css_tag == CSS_ID_script)) goto pretag; if (eot) goto end_of_tag; /* parse attributes */ first_attr = NULL; pattr = &first_attr; for (;;) { qe_skip_spaces(&p); if (*p == '\0' || *p == '/') break; get_str(&p, attr_name, sizeof(attr_name), "=/"); if (s->ignore_case) qe_strtolower(attr_name, sizeof(attr_name), attr_name); if (*p == '=') { int och, ch; p++; qe_skip_spaces(&p); och = *p; /* in html, we can put non string values */ if (och != '\'' && och != '\"') { if (!s->html_syntax) xml_error(s, "string expected for attribute '%s'", attr_name); q = value; while (*p != '\0' && !strchr(" \t\n\r<>", *p)) { ch = parse_entity(&p); if ((q - value) < (int)sizeof(value) - 1) *q++ = ch; } *q = '\0'; } else { p++; q = value; while (*p != och && *p != '\0' && *p != '<') { ch = parse_entity(&p); if ((q - value) < (int)sizeof(value) - 1) *q++ = ch; } *q = '\0'; if (*p != och) { xml_error(s, "malformed string in attribute '%s'", attr_name); } else { p++; } } } else { value[0] = '\0'; } attr = box_new_attr(css_new_ident(attr_name), value); if (attr) { *pattr = attr; pattr = &attr->next; } } /* close some tags (correct HTML mistakes) */ if (s->html_syntax) { const HTMLClosedTags *ct; ct = html_closed_tags; for (;;) { if (!ct->tag) break; if (css_tag == ct->tag) { box1 = s->box; while (box1 != NULL && css_get_enum(css_ident_str(box1->tag), ct->tag_closed) >= 0) { html_eval_tag(s, box1); box1 = box1->parent; } if (box1) { s->box = box1; } break; } ct++; } } /* create the new box and add it */ box = css_new_box(css_tag, NULL); box->attrs = first_attr; if (!s->box) { s->root_box = box; } else { css_make_child_box(s->box); css_add_box(s->box, box); } s->box = box; if ((s->flags & XML_DOCBOOK) && css_tag == CSS_ID_programlisting) { pretag: pstrcpy(s->pretag, sizeof(s->pretag), tag); s->pretaglen = strlen(s->pretag); return XML_STATE_PRETAG; } len = strlen(buf); /* end of tag. If html, check also some common mistakes. FORM is considered as self closing to avoid any content problems */ if ((len > 0 && buf[len - 1] == '/') || (s->html_syntax && (css_tag == CSS_ID_br || css_tag == CSS_ID_hr || css_tag == CSS_ID_meta || css_tag == CSS_ID_link || css_tag == CSS_ID_form || css_tag == CSS_ID_base || css_tag == CSS_ID_input || css_tag == CSS_ID_basefont || css_tag == CSS_ID_img))) { end_of_tag: box1 = s->box; if (box1) { if (s->html_syntax) { if (css_tag != CSS_ID_NIL) { /* close all non matching tags */ while (box1 != NULL && box1->tag != css_tag) { html_eval_tag(s, box1); box1 = box1->parent; } } if (!box1) { if (css_tag != CSS_ID_form) xml_error(s, "unmatched closing tag ", css_ident_str(css_tag)); } else { html_eval_tag(s, box1); s->box = box1->parent; } } else { if (css_tag != CSS_ID_NIL && box1->tag != css_tag) { xml_error(s, "unmatched closing tag for <%s>", css_ident_str(css_tag), css_ident_str(box1->tag)); } else { if (s->is_html) html_eval_tag(s, box1); s->box = box1->parent; } } } } return XML_STATE_TEXT; } // XXX: should take length? static void flush_text(XMLState *s, const char *buf) { CSSBox *box, *box1; box = s->box; /* if no current box, discard */ if (!box) return; if (buf[0] == '\0') return; css_make_child_box(box); if (box->u.child.first != NULL) { /* non empty box: we add an anonymous box */ box1 = css_new_box(CSS_ID_NIL, NULL); css_add_box(box, box1); } else { box1 = box; } // XXX: should pass length? css_set_text_string(box1, buf); } static void flush_text_buffer(XMLState *s, struct EditBuffer *b, int offset0, int offset1) { CSSBox *box, *box1; box = s->box; /* if no current box, discard */ if (!box) return; if (offset0 >= offset1) return; css_make_child_box(box); if (box->u.child.first != NULL) { /* non empty box: we add an anonymous box */ box1 = css_new_box(CSS_ID_NIL, NULL); css_add_box(box, box1); } else { box1 = box; } css_set_text_buffer(box1, b, offset0, offset1, 1); } static int xml_tagcmp(const char *s1, const char *s2) { while (*s2) { unsigned char uc1 = *s1++; unsigned char uc2 = *s2++; int d = uc2 - qe_tolower(uc1); if (d) return d; } /* XXX: Should only return 0 if (*s1) is separator */ return 0; } static int xml_parse_internal(XMLState *s, const char *buf_start, int buf_len, struct EditBuffer *b, int offset_start) { int offset, offset0, text_offset_start, ret, offset_end; const char *buf_end, *buf; char32_t ch; buf = buf_start; buf_end = buf + buf_len; offset = offset_start; offset_end = offset_start + buf_len; offset0 = 0; /* not used */ text_offset_start = 0; /* not used */ for (;;) { if (buf) { if (buf >= buf_end) break; s->charset_state.p = (const u8*)buf; ch = s->charset_state.decode_func(&s->charset_state); buf = (const char *)s->charset_state.p; } else { if (offset >= offset_end) break; offset0 = offset; ch = eb_nextc(b, offset, &offset); } /* increment line number to signal errors */ if (ch == '\n') { /* well, should add counter, but we test abort here */ if (s->abort_func(s->abort_opaque)) return -1; s->line_num++; } switch (s->state) { case XML_STATE_TAG: if (ch == '>') { strbuf_addch(&s->str, '\0'); ret = parse_tag(s, s->str.buf); switch (ret) { default: case XML_STATE_TEXT: xml_text: strbuf_reset(&s->str); s->state = XML_STATE_TEXT; text_offset_start = offset; break; case XML_STATE_PRETAG: strbuf_reset(&s->str); s->state = XML_STATE_PRETAG; text_offset_start = offset; break; } } else { strbuf_addch(&s->str, ch); /* test comment */ if (s->str.size == 3 && s->str.buf[0] == '!' && s->str.buf[1] == '-' && s->str.buf[2] == '-') { s->state = XML_STATE_COMMENT; } } break; case XML_STATE_TEXT: if (ch == '<') { /* XXX: not strictly correct with comments : should not flush if comment */ if (buf) { strbuf_addch(&s->str, '\0'); flush_text(s, s->str.buf); strbuf_reset(&s->str); } else { flush_text_buffer(s, b, text_offset_start, offset0); } s->state = XML_STATE_TAG; } else { if (buf) { /* evaluate entities */ if (ch == '&') { buf--; ch = parse_entity(&buf); } strbuf_addch(&s->str, ch); } } break; case XML_STATE_COMMENT: if (ch == '-') s->state = XML_STATE_COMMENT1; break; case XML_STATE_COMMENT1: if (ch == '-') s->state = XML_STATE_COMMENT2; else s->state = XML_STATE_COMMENT; break; case XML_STATE_COMMENT2: if (ch == '>') { goto xml_text; } else if (ch != '-') { s->state = XML_STATE_COMMENT; } break; case XML_STATE_PRETAG: { int len, taglen; strbuf_addch(&s->str, ch); taglen = s->pretaglen + 2; len = s->str.size - taglen; if (len >= 0 && s->str.buf[len] == '<' && s->str.buf[len + 1] == '/' && !xml_tagcmp(s->str.buf + len + 2, s->pretag)) { s->str.buf[len] = '\0'; if (!xml_tagcmp(s->pretag, "style")) { if (s->style_sheet) { CSSParseState b1, *bp = &b1; bp->error_opaque = s->error_opaque; bp->ptr = s->str.buf; bp->filename = s->filename; bp->line_num = s->line_num; /* XXX: incorrect */ bp->ignore_case = s->ignore_case; css_parse_style_sheet(s->style_sheet, bp); } } else if (!xml_tagcmp(s->pretag, "script")) { /* XXX: handle script */ } else { /* just add the content */ if (buf) { flush_text(s, s->str.buf); } else { /* XXX: would be incorrect if non ascii chars */ flush_text_buffer(s, b, text_offset_start, offset - taglen); } strbuf_reset(&s->str); if (s->box) s->box = s->box->parent; } s->state = XML_STATE_WAIT_EOT; } } break; case XML_STATE_WAIT_EOT: /* wait end of tag */ if (ch == '>') goto xml_text; break; } } /* CG: incorrect if parsing from buffer */ return buf - buf_start; } int xml_parse(XMLState *s, char *buf, int buf_len) { int len, len1; if (s->lookahead_size > 0) { /* fill the lookahead buffer with the content of 'buf' and handle all chars which are in lookahead_buf */ len = LOOKAHEAD_SIZE - 1; if (len > buf_len) len = buf_len; memcpy(s->lookahead_buf + s->lookahead_size, buf, len); len += s->lookahead_size; /* total chars in lookahead buffer */ /* parse only if enough chars for lookahead */ len -= (LOOKAHEAD_SIZE - 1); if (len > 0) { len = xml_parse_internal(s, s->lookahead_buf, len, NULL, 0); if (len < 0) return -1; len1 = len - s->lookahead_size; if (len1 <= 0) { s->lookahead_size -= len; memmove(s->lookahead_buf, s->lookahead_buf + len, s->lookahead_size); } else { buf_len -= len1; buf += len1; s->lookahead_size = 0; } /* if could not parse everything, no need to go further */ if (s->lookahead_size > 0) return 0; } else { /* all chars were put in lookahead buffer */ s->lookahead_size = len + (LOOKAHEAD_SIZE - 1); return 0; } } /* now no chars are left in the lookahead buffer so we can parse at full speed */ len1 = buf_len - (LOOKAHEAD_SIZE - 1); if (len1 > 0) { len = xml_parse_internal(s, buf, len1, NULL, 0); if (len < 0) return -1; buf_len -= len; buf += len; } /* put all the remaining chars in the lookahead buffer */ memcpy(s->lookahead_buf, buf, buf_len); s->lookahead_size = buf_len; return 0; } CSSBox *xml_end(XMLState **sp) { XMLState *s = *sp; CSSBox *root_box; if (!s) return NULL; /* flush the lookahead buffer */ if (s->lookahead_size > 0) { /* mark the end to stop parsing function */ s->lookahead_buf[s->lookahead_size] = '\0'; xml_parse_internal(s, s->lookahead_buf, s->lookahead_size, NULL, 0); } if (s->charset) { charset_decode_close(&s->charset_state); s->charset = NULL; } strbuf_reset(&s->str); root_box = s->root_box; qe_free(&s); return root_box; } // XXX: move to qe source tree // XXX: should pass pass function to read a code point to xml_parse_internal, using an opaque /* XML in edit buffer parsing */ CSSBox *xml_parse_buffer(struct EditBuffer *b, const char *name, int offset_start, int offset_end, CSSStyleSheet *style_sheet, int flags, CSSAbortFunc *abort_func, void *abort_opaque, void *error_opaque) { XMLState *s; CSSBox *box; int ret; s = xml_begin(style_sheet, flags, abort_func, abort_opaque, error_opaque, name, NULL); ret = xml_parse_internal(s, NULL, offset_end - offset_start, b, offset_start); box = xml_end(&s); if (ret < 0) { css_delete_box(&box); return NULL; } return box; }