xy z
for all tags "p" allowing PCDATA */ for ( i = 0; i < sizeof(allowPCData)/sizeof(allowPCData[0]); i++ ) { if ( xmlStrEqual(lastChild->name, BAD_CAST allowPCData[i]) ) { return(0); } } } return(1); } /** * htmlNewDocNoDtD: * @URI: URI for the dtd, or NULL * @ExternalID: the external ID of the DTD, or NULL * * Creates a new HTML document without a DTD node if @URI and @ExternalID * are NULL * * Returns a new document, do not initialize the DTD if not provided */ htmlDocPtr htmlNewDocNoDtD(const xmlChar *URI, const xmlChar *ExternalID) { xmlDocPtr cur; /* * Allocate a new document and fill the fields. */ cur = (xmlDocPtr) xmlMalloc(sizeof(xmlDoc)); if (cur == NULL) return(NULL); memset(cur, 0, sizeof(xmlDoc)); cur->type = XML_HTML_DOCUMENT_NODE; cur->version = NULL; cur->intSubset = NULL; cur->doc = cur; cur->name = NULL; cur->children = NULL; cur->extSubset = NULL; cur->oldNs = NULL; cur->encoding = NULL; cur->standalone = 1; cur->compression = 0; cur->ids = NULL; cur->refs = NULL; cur->_private = NULL; cur->charset = XML_CHAR_ENCODING_UTF8; cur->properties = XML_DOC_HTML | XML_DOC_USERBUILT; if ((ExternalID != NULL) || (URI != NULL)) { xmlDtdPtr intSubset; intSubset = xmlCreateIntSubset(cur, BAD_CAST "html", ExternalID, URI); if (intSubset == NULL) { xmlFree(cur); return(NULL); } } if ((xmlRegisterCallbacks) && (xmlRegisterNodeDefaultValue)) xmlRegisterNodeDefaultValue((xmlNodePtr)cur); return(cur); } /** * htmlNewDoc: * @URI: URI for the dtd, or NULL * @ExternalID: the external ID of the DTD, or NULL * * Creates a new HTML document * * Returns a new document */ htmlDocPtr htmlNewDoc(const xmlChar *URI, const xmlChar *ExternalID) { if ((URI == NULL) && (ExternalID == NULL)) return(htmlNewDocNoDtD( BAD_CAST "http://www.w3.org/TR/REC-html40/loose.dtd", BAD_CAST "-//W3C//DTD HTML 4.0 Transitional//EN")); return(htmlNewDocNoDtD(URI, ExternalID)); } /************************************************************************ * * * The parser itself * * Relates to http://www.w3.org/TR/html40 * * * ************************************************************************/ /************************************************************************ * * * The parser itself * * * ************************************************************************/ static const xmlChar * htmlParseNameComplex(xmlParserCtxtPtr ctxt); /** * htmlParseHTMLName: * @ctxt: an HTML parser context * * parse an HTML tag or attribute name, note that we convert it to lowercase * since HTML names are not case-sensitive. * * Returns the Tag Name parsed or NULL */ static const xmlChar * htmlParseHTMLName(htmlParserCtxtPtr ctxt, int attr) { const xmlChar *ret; int nbchar = 0; int c, l; int stop = attr ? '=' : 0; xmlChar buf[HTML_PARSER_BUFFER_SIZE]; c = CUR_CHAR(l); while ((c != 0) && (c != '/') && (c != '>') && ((nbchar == 0) || (c != stop)) && (!IS_WS_HTML(c))) { if (nbchar + l <= HTML_PARSER_BUFFER_SIZE) { if ((c >= 'A') && (c <= 'Z')) { buf[nbchar++] = c + 0x20; } else { COPY_BUF(buf, nbchar, c); } } NEXTL(l); c = CUR_CHAR(l); } ret = xmlDictLookup(ctxt->dict, buf, nbchar); if (ret == NULL) htmlErrMemory(ctxt); return(ret); } /** * htmlParseName: * @ctxt: an HTML parser context * * parse an HTML name, this routine is case sensitive. * * Returns the Name parsed or NULL */ static const xmlChar * htmlParseName(htmlParserCtxtPtr ctxt) { const xmlChar *in; const xmlChar *ret; int count = 0; GROW; /* * Accelerator for simple ASCII names */ in = ctxt->input->cur; if (((*in >= 0x61) && (*in <= 0x7A)) || ((*in >= 0x41) && (*in <= 0x5A)) || (*in == '_') || (*in == ':')) { in++; while (((*in >= 0x61) && (*in <= 0x7A)) || ((*in >= 0x41) && (*in <= 0x5A)) || ((*in >= 0x30) && (*in <= 0x39)) || (*in == '_') || (*in == '-') || (*in == ':') || (*in == '.')) in++; if (in == ctxt->input->end) return(NULL); if ((*in > 0) && (*in < 0x80)) { count = in - ctxt->input->cur; ret = xmlDictLookup(ctxt->dict, ctxt->input->cur, count); if (ret == NULL) htmlErrMemory(ctxt); ctxt->input->cur = in; ctxt->input->col += count; return(ret); } } return(htmlParseNameComplex(ctxt)); } static const xmlChar * htmlParseNameComplex(xmlParserCtxtPtr ctxt) { int len = 0, l; int c; int maxLength = (ctxt->options & XML_PARSE_HUGE) ? XML_MAX_TEXT_LENGTH : XML_MAX_NAME_LENGTH; const xmlChar *base = ctxt->input->base; const xmlChar *ret; /* * Handler for more complex cases */ c = CUR_CHAR(l); if ((c == ' ') || (c == '>') || (c == '/') || /* accelerators */ (!IS_LETTER(c) && (c != '_') && (c != ':'))) { return(NULL); } while ((c != ' ') && (c != '>') && (c != '/') && /* test bigname.xml */ ((IS_LETTER(c)) || (IS_DIGIT(c)) || (c == '.') || (c == '-') || (c == '_') || (c == ':') || (IS_COMBINING(c)) || (IS_EXTENDER(c)))) { len += l; if (len > maxLength) { htmlParseErr(ctxt, XML_ERR_NAME_TOO_LONG, "name too long", NULL, NULL); return(NULL); } NEXTL(l); c = CUR_CHAR(l); if (ctxt->input->base != base) { /* * We changed encoding from an unknown encoding * Input buffer changed location, so we better start again */ return(htmlParseNameComplex(ctxt)); } } if (ctxt->input->cur - ctxt->input->base < len) { /* Sanity check */ htmlParseErr(ctxt, XML_ERR_INTERNAL_ERROR, "unexpected change of input buffer", NULL, NULL); return (NULL); } ret = xmlDictLookup(ctxt->dict, ctxt->input->cur - len, len); if (ret == NULL) htmlErrMemory(ctxt); return(ret); } #include "html5ent.inc" #define ENT_F_SEMICOLON 0x80u #define ENT_F_SUBTABLE 0x40u #define ENT_F_ALL 0xC0u static const xmlChar * htmlFindEntityPrefix(const xmlChar *string, size_t slen, int isAttr, int *nlen, int *rlen) { const xmlChar *match = NULL; unsigned left, right; int first = string[0]; size_t matchLen = 0; size_t soff = 1; if (slen < 2) return(NULL); if (((first < 'A') || (first > 'Z')) && ((first < 'a') || (first > 'z'))) return(NULL); /* * Look up range by first character */ first &= 63; left = htmlEntAlpha[first*3] | htmlEntAlpha[first*3+1] << 8; right = left + htmlEntAlpha[first*3+2]; /* * Binary search */ while (left < right) { const xmlChar *bytes; unsigned mid; size_t len; int cmp; mid = left + (right - left) / 2; bytes = htmlEntStrings + htmlEntValues[mid]; len = bytes[0] & ~ENT_F_ALL; cmp = string[soff] - bytes[1]; if (cmp == 0) { if (slen < len) { cmp = strncmp((const char *) string + soff + 1, (const char *) bytes + 2, slen - 1); /* Prefix can never match */ if (cmp == 0) break; } else { cmp = strncmp((const char *) string + soff + 1, (const char *) bytes + 2, len - 1); } } if (cmp < 0) { right = mid; } else if (cmp > 0) { left = mid + 1; } else { int term = soff + len < slen ? string[soff + len] : 0; int isAlnum, isTerm; isAlnum = (((term >= 'A') && (term <= 'Z')) || ((term >= 'a') && (term <= 'z')) || ((term >= '0') && (term <= '9'))); isTerm = ((term == ';') || ((bytes[0] & ENT_F_SEMICOLON) && ((!isAttr) || ((!isAlnum) && (term != '='))))); if (isTerm) { match = bytes + len + 1; matchLen = soff + len; if (term == ';') matchLen += 1; } if (bytes[0] & ENT_F_SUBTABLE) { if (isTerm) match += 2; if ((isAlnum) && (soff + len < slen)) { left = mid + bytes[len + 1]; right = left + bytes[len + 2]; soff += len; continue; } } break; } } if (match == NULL) return(NULL); *nlen = matchLen; *rlen = match[0]; return(match + 1); } /** * htmlParseHTMLAttribute: * @ctxt: an HTML parser context * @stop: a char stop value * * parse an HTML attribute value till the stop (quote), if * stop is 0 then it stops at the first space * * Returns the attribute parsed or NULL */ static xmlChar * htmlParseHTMLAttribute(htmlParserCtxtPtr ctxt, int stop) { xmlChar *buffer = NULL; int buffer_size = 0; int maxLength = (ctxt->options & XML_PARSE_HUGE) ? XML_MAX_HUGE_LENGTH : XML_MAX_TEXT_LENGTH; xmlChar *out = NULL; /* * allocate a translation buffer. */ buffer_size = HTML_PARSER_BUFFER_SIZE; buffer = xmlMalloc(buffer_size); if (buffer == NULL) { htmlErrMemory(ctxt); return(NULL); } out = buffer; /* * Ok loop until we reach one of the ending chars */ while ((PARSER_STOPPED(ctxt) == 0) && (ctxt->input->cur < ctxt->input->end) && ((stop == 0) || (CUR != stop))) { if ((stop == 0) && (CUR == '>')) break; if ((stop == 0) && (IS_WS_HTML(CUR))) break; if (out - buffer > buffer_size - 100) { int indx = out - buffer; growBuffer(buffer); out = &buffer[indx]; } GROW; if (CUR == '&') { if (NXT(1) == '#') { unsigned int c; int bits; c = htmlParseCharRef(ctxt); if (c < 0x80) { *out++ = c; bits= -6; } else if (c < 0x800) { *out++ =((c >> 6) & 0x1F) | 0xC0; bits= 0; } else if (c < 0x10000) { *out++ =((c >> 12) & 0x0F) | 0xE0; bits= 6; } else { *out++ =((c >> 18) & 0x07) | 0xF0; bits= 12; } for ( ; bits >= 0; bits-= 6) { *out++ = ((c >> bits) & 0x3F) | 0x80; } } else { const xmlChar *repl; int nameLen, replLen; SKIP(1); repl = htmlFindEntityPrefix(CUR_PTR, ctxt->input->end - CUR_PTR, /* isAttr */ 1, &nameLen, &replLen); if (repl == NULL) { *out++ = '&'; } else { memcpy(out, repl, replLen); out += replLen; SKIP(nameLen); } } } else { unsigned int c; int bits, l; c = CUR_CHAR(l); if (c < 0x80) { *out++ = c; bits= -6; } else if (c < 0x800) { *out++ =((c >> 6) & 0x1F) | 0xC0; bits= 0; } else if (c < 0x10000) { *out++ =((c >> 12) & 0x0F) | 0xE0; bits= 6; } else { *out++ =((c >> 18) & 0x07) | 0xF0; bits= 12; } for ( ; bits >= 0; bits-= 6) { *out++ = ((c >> bits) & 0x3F) | 0x80; } NEXTL(l); } if (out - buffer > maxLength) { htmlParseErr(ctxt, XML_ERR_ATTRIBUTE_NOT_FINISHED, "attribute value too long\n", NULL, NULL); xmlFree(buffer); return(NULL); } } *out = 0; return(buffer); } /** * htmlParseEntityRef: * @ctxt: an HTML parser context * @str: location to store the entity name * * DEPRECATED: Internal function, don't use. * * parse an HTML ENTITY references * * [68] EntityRef ::= '&' Name ';' * * Returns the associated htmlEntityDescPtr if found, or NULL otherwise, * if non-NULL *str will have to be freed by the caller. */ const htmlEntityDesc * htmlParseEntityRef(htmlParserCtxtPtr ctxt, const xmlChar **str) { const xmlChar *name; const htmlEntityDesc * ent = NULL; if (str != NULL) *str = NULL; if ((ctxt == NULL) || (ctxt->input == NULL)) return(NULL); if (CUR == '&') { SKIP(1); name = htmlParseName(ctxt); if (name == NULL) { htmlParseErr(ctxt, XML_ERR_NAME_REQUIRED, "htmlParseEntityRef: no name\n", NULL, NULL); } else { GROW; if (CUR == ';') { if (str != NULL) *str = name; /* * Lookup the entity in the table. */ ent = htmlEntityLookup(name); if (ent != NULL) /* OK that's ugly !!! */ SKIP(1); } else { htmlParseErr(ctxt, XML_ERR_ENTITYREF_SEMICOL_MISSING, "htmlParseEntityRef: expecting ';'\n", NULL, NULL); if (str != NULL) *str = name; } } } return(ent); } /** * htmlParseAttValue: * @ctxt: an HTML parser context * * parse a value for an attribute * Note: the parser won't do substitution of entities here, this * will be handled later in xmlStringGetNodeList, unless it was * asked for ctxt->replaceEntities != 0 * * Returns the AttValue parsed or NULL. */ static xmlChar * htmlParseAttValue(htmlParserCtxtPtr ctxt) { xmlChar *ret = NULL; if (CUR == '"') { SKIP(1); ret = htmlParseHTMLAttribute(ctxt, '"'); if (CUR == '"') SKIP(1); } else if (CUR == '\'') { SKIP(1); ret = htmlParseHTMLAttribute(ctxt, '\''); if (CUR == '\'') SKIP(1); } else { /* * That's an HTMLism, the attribute value may not be quoted */ ret = htmlParseHTMLAttribute(ctxt, 0); } return(ret); } static void htmlCharDataSAXCallback(htmlParserCtxtPtr ctxt, const xmlChar *buf, int size, int mode) { if ((ctxt->sax == NULL) || (ctxt->disableSAX)) return; if ((mode == 0) || (mode == DATA_RCDATA)) { if (areBlanks(ctxt, buf, size)) { if (ctxt->keepBlanks) { if (ctxt->sax->characters != NULL) ctxt->sax->characters(ctxt->userData, buf, size); } else { if (ctxt->sax->ignorableWhitespace != NULL) ctxt->sax->ignorableWhitespace(ctxt->userData, buf, size); } } else { htmlCheckParagraph(ctxt); if (ctxt->sax->characters != NULL) ctxt->sax->characters(ctxt->userData, buf, size); } } else { if (ctxt->sax->cdataBlock != NULL) { /* * Insert as CDATA, which is the same as HTML_PRESERVE_NODE */ ctxt->sax->cdataBlock(ctxt->userData, buf, size); } else if (ctxt->sax->characters != NULL) { ctxt->sax->characters(ctxt->userData, buf, size); } } } /** * htmlParseCharData: * @ctxt: an HTML parser context * @terminate: true if the input buffer is complete * * Parse character data and references. */ static int htmlParseCharData(htmlParserCtxtPtr ctxt, int terminate) { xmlChar buf[HTML_PARSER_BIG_BUFFER_SIZE + 6]; int nbchar = 0; int complete = 0; int res = 0; int cur, l, mode; mode = ctxt->endCheckState; while ((!PARSER_STOPPED(ctxt)) && (ctxt->input->cur < ctxt->input->end)) { cur = CUR_CHAR(l); /* * Check for end of text data */ if ((cur == '<') && (mode != DATA_PLAINTEXT)) { int j, len; if (mode == 0) break; j = 1; len = ctxt->input->end - ctxt->input->cur; if (j < len) { if ((mode == DATA_SCRIPT) && (NXT(j) == '!')) { /* Check for comment start */ j += 1; if ((j < len) && (NXT(j) == '-')) { j += 1; if ((j < len) && (NXT(j) == '-')) mode = DATA_SCRIPT_ESC1; } } else { int i = 0; int solidus = 0; /* Check for tag */ if (NXT(j) == '/') { j += 1; solidus = 1; } if ((solidus) || (mode == DATA_SCRIPT_ESC1)) { while ((j < len) && (ctxt->name[i] != 0) && (ctxt->name[i] == (NXT(j) | 32))) { i += 1; j += 1; } if ((ctxt->name[i] == 0) && (j < len)) { int c = NXT(j); if ((c == '>') || (c == '/') || (IS_WS_HTML(c))) { if ((mode == DATA_SCRIPT_ESC1) && (!solidus)) { mode = DATA_SCRIPT_ESC2; } else if (mode == DATA_SCRIPT_ESC2) { mode = DATA_SCRIPT_ESC1; } else { complete = 1; res = 1; break; } } } } } } /* Push parser */ if ((!terminate) && (j >= len)) { res = 1; break; } } else if ((cur == '-') && ((mode == DATA_SCRIPT_ESC1) || (mode == DATA_SCRIPT_ESC2))) { int len = ctxt->input->end - ctxt->input->cur; int j = 1; /* Check for comment end */ if ((j < len) && (NXT(j) == '-')) { j += 1; if ((j < len) && (NXT(j) == '>')) mode = DATA_SCRIPT; } /* Push parser */ if ((!terminate) && (j >= len)) { res = 1; break; } } else if ((cur == '&') && ((mode == 0) || (mode == DATA_RCDATA))) { break; } COPY_BUF(buf,nbchar,cur); NEXTL(l); if (nbchar >= HTML_PARSER_BIG_BUFFER_SIZE) { buf[nbchar] = 0; htmlCharDataSAXCallback(ctxt, buf, nbchar, mode); nbchar = 0; SHRINK; } } if (nbchar != 0) { buf[nbchar] = 0; htmlCharDataSAXCallback(ctxt, buf, nbchar, mode); } if (complete) ctxt->endCheckState = 0; else ctxt->endCheckState = mode; return(res); } /** * htmlParseComment: * @ctxt: an HTML parser context * @bogus: true if this is a bogus comment * * Parse an HTML comment */ static void htmlParseComment(htmlParserCtxtPtr ctxt, int bogus) { xmlChar *buf = NULL; int len; int size = HTML_PARSER_BUFFER_SIZE; int cur, l; int maxLength = (ctxt->options & XML_PARSE_HUGE) ? XML_MAX_HUGE_LENGTH : XML_MAX_TEXT_LENGTH; xmlParserInputState state; state = ctxt->instate; ctxt->instate = XML_PARSER_COMMENT; buf = xmlMalloc(size); if (buf == NULL) { htmlErrMemory(ctxt); return; } len = 0; buf[len] = 0; cur = CUR_CHAR(l); if (!bogus) { if (cur == '>') { SKIP(1); goto done; } else if ((cur == '-') && (NXT(1) == '>')) { SKIP(2); goto done; } } while (cur != 0) { if (bogus) { if (cur == '>') { SKIP(1); break; } } else { if (cur == '-') { size_t avail = ctxt->input->end - ctxt->input->cur; if (avail < 2) { SKIP(1); break; } else if (NXT(1) == '-') { if (avail < 3) { SKIP(2); break; } else if (NXT(2) == '>') { SKIP(3); break; } else if (NXT(2) == '!') { if (avail < 4) { SKIP(3); break; } else if (NXT(3) == '>') { SKIP(4); break; } } } } } if (len + 5 >= size) { xmlChar *tmp; size *= 2; tmp = (xmlChar *) xmlRealloc(buf, size); if (tmp == NULL) { xmlFree(buf); htmlErrMemory(ctxt); ctxt->instate = state; return; } buf = tmp; } COPY_BUF(buf,len,cur); if (len > maxLength) { htmlParseErr(ctxt, XML_ERR_COMMENT_NOT_FINISHED, "comment too long", NULL, NULL); xmlFree(buf); ctxt->instate = state; return; } NEXTL(l); cur = CUR_CHAR(l); } done: buf[len] = 0; if ((ctxt->sax != NULL) && (ctxt->sax->comment != NULL) && (!ctxt->disableSAX)) ctxt->sax->comment(ctxt->userData, buf); xmlFree(buf); ctxt->instate = state; return; } static const short htmlC1Remap[32] = { 0x20AC, 0x0081, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008D, 0x017D, 0x008F, 0x0090, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x009D, 0x017E, 0x0178 }; /** * htmlParseCharRef: * @ctxt: an HTML parser context * * DEPRECATED: Internal function, don't use. * * parse Reference declarations * * [66] CharRef ::= '' [0-9]+ ';' | * '' [0-9a-fA-F]+ ';' * * Returns the value parsed (as an int) */ int htmlParseCharRef(htmlParserCtxtPtr ctxt) { int val = 0; if ((ctxt == NULL) || (ctxt->input == NULL)) return(0); if ((CUR == '&') && (NXT(1) == '#') && ((NXT(2) == 'x') || NXT(2) == 'X')) { SKIP(3); while (1) { int c = CUR; if ((c >= '0') && (c <= '9')) { c -= '0'; } else if ((c >= 'a') && (c <= 'f')) { c = (c - 'a') + 10; } else if ((c >= 'A') && (c <= 'F')) { c = (c - 'A') + 10; } else { break; } val = val * 16 + c; if (val >= 0x110000) val = 0x110000; NEXT; } if (CUR == ';') SKIP(1); } else if ((CUR == '&') && (NXT(1) == '#')) { SKIP(2); while (1) { int c = CUR; if ((c < '0') || (c > '9')) break; val = val * 10 + (c - '0'); if (val >= 0x110000) val = 0x110000; NEXT; } if (CUR == ';') SKIP(1); } /* * Remap C1 control characters */ if ((val >= 0x80) && (val < 0xA0)) { val = htmlC1Remap[val - 0x80]; } else if ((val <= 0) || ((val >= 0xD800) && (val < 0xE000)) || (val > 0x10FFFF)) { val = 0xFFFD; } return(val); } /** * htmlParseDoctypeLiteral: * @ctxt: an HTML parser context * * Parse a DOCTYPE SYTSTEM or PUBLIC literal. * * Returns the literal or NULL in case of error. */ static xmlChar * htmlParseDoctypeLiteral(htmlParserCtxtPtr ctxt) { xmlChar *buf = NULL; int len; int size = HTML_PARSER_BUFFER_SIZE; int quote, cur, l; int maxLength = (ctxt->options & XML_PARSE_HUGE) ? XML_MAX_TEXT_LENGTH : XML_MAX_NAME_LENGTH; if ((CUR != '"') && (CUR != '\'')) return(NULL); quote = CUR; NEXT; buf = xmlMalloc(size); if (buf == NULL) { htmlErrMemory(ctxt); return(NULL); } len = 0; while (ctxt->input->cur < ctxt->input->end) { cur = CUR_CHAR(l); if (cur == '>') break; if (cur == quote) { SKIP(1); break; } if (len + 5 >= size) { xmlChar *tmp; size *= 2; tmp = (xmlChar *) xmlRealloc(buf, size); if (tmp == NULL) { xmlFree(buf); htmlErrMemory(ctxt); return(NULL); } buf = tmp; } COPY_BUF(buf,len,cur); if (len > maxLength) { htmlParseErr(ctxt, XML_ERR_RESOURCE_LIMIT, "identifier too long", NULL, NULL); xmlFree(buf); return(NULL); } NEXTL(l); } buf[len] = 0; return(buf); } /** * htmlParseDocTypeDecl: * @ctxt: an HTML parser context * * Parse a DOCTYPE declaration. */ static void htmlParseDocTypeDecl(htmlParserCtxtPtr ctxt) { xmlChar *name = NULL; xmlChar *publicId = NULL; xmlChar *URI = NULL; int nameCap, nameSize; int maxLength = (ctxt->options & XML_PARSE_HUGE) ? XML_MAX_TEXT_LENGTH : XML_MAX_NAME_LENGTH; /* * We know that 'input->cur < ctxt->input->end) { int l; int c = CUR_CHAR(l); if (c == '>') break; if (nameSize + 5 > nameCap) { size_t newCap = nameCap ? nameCap * 2 : 32; xmlChar *tmp = xmlRealloc(name, newCap); if (tmp == NULL) { htmlErrMemory(ctxt); xmlFree(name); return; } name = tmp; nameCap = newCap; } if (c < 0x80) { if (IS_WS_HTML(c)) break; if ((ctxt->options & HTML_PARSE_HTML5) && (c >= 'A') && (c <= 'Z')) c += 32; name[nameSize++] = c; } else { COPY_BUF(name, nameSize, c); } if (nameSize > maxLength) { htmlParseErr(ctxt, XML_ERR_RESOURCE_LIMIT, "identifier too long", NULL, NULL); goto bogus; } NEXTL(l); } if (name != NULL) name[nameSize] = 0; /* * Check that upper(name) == "HTML" !!!!!!!!!!!!! */ SKIP_BLANKS; /* * Check for SystemID and publicId */ if ((UPPER == 'P') && (UPP(1) == 'U') && (UPP(2) == 'B') && (UPP(3) == 'L') && (UPP(4) == 'I') && (UPP(5) == 'C')) { SKIP(6); SKIP_BLANKS; publicId = htmlParseDoctypeLiteral(ctxt); if (publicId == NULL) goto bogus; SKIP_BLANKS; URI = htmlParseDoctypeLiteral(ctxt); } else if ((UPPER == 'S') && (UPP(1) == 'Y') && (UPP(2) == 'S') && (UPP(3) == 'T') && (UPP(4) == 'E') && (UPP(5) == 'M')) { SKIP(6); SKIP_BLANKS; URI = htmlParseDoctypeLiteral(ctxt); } bogus: /* Ignore bogus content */ while (ctxt->input->cur < ctxt->input->end) { int c = CUR; NEXT; if (c == '>') break; } /* * Create or update the document accordingly to the DOCTYPE */ if ((ctxt->sax != NULL) && (ctxt->sax->internalSubset != NULL) && (!ctxt->disableSAX)) ctxt->sax->internalSubset(ctxt->userData, name, publicId, URI); xmlFree(name); xmlFree(URI); xmlFree(publicId); } /** * htmlParseAttribute: * @ctxt: an HTML parser context * @value: a xmlChar ** used to store the value of the attribute * * parse an attribute * * [41] Attribute ::= Name Eq AttValue * * [25] Eq ::= S? '=' S? * * With namespace: * * [NS 11] Attribute ::= QName Eq AttValue * * Also the case QName == xmlns:??? is handled independently as a namespace * definition. * * Returns the attribute name, and the value in *value. */ static const xmlChar * htmlParseAttribute(htmlParserCtxtPtr ctxt, xmlChar **value) { const xmlChar *name; xmlChar *val = NULL; *value = NULL; name = htmlParseHTMLName(ctxt, 1); if (name == NULL) return(NULL); /* * read the value */ SKIP_BLANKS; if (CUR == '=') { SKIP(1); SKIP_BLANKS; val = htmlParseAttValue(ctxt); } *value = val; return(name); } /** * htmlCheckEncoding: * @ctxt: an HTML parser context * @attvalue: the attribute value * * Checks an http-equiv attribute from a Meta tag to detect * the encoding * If a new encoding is detected the parser is switched to decode * it and pass UTF8 */ static void htmlCheckEncoding(htmlParserCtxtPtr ctxt, const xmlChar *attvalue) { const xmlChar *encoding; xmlChar *copy; if (!attvalue) return; encoding = xmlStrcasestr(attvalue, BAD_CAST"charset"); if (encoding != NULL) { encoding += 7; } /* * skip blank */ if (encoding && IS_WS_HTML(*encoding)) encoding = xmlStrcasestr(attvalue, BAD_CAST"="); if (encoding && *encoding == '=') { encoding ++; copy = xmlStrdup(encoding); if (copy == NULL) htmlErrMemory(ctxt); xmlSetDeclaredEncoding(ctxt, copy); } } /** * htmlCheckMeta: * @ctxt: an HTML parser context * @atts: the attributes values * * Checks an attributes from a Meta tag */ static void htmlCheckMeta(htmlParserCtxtPtr ctxt, const xmlChar **atts) { int i; const xmlChar *att, *value; int http = 0; const xmlChar *content = NULL; if ((ctxt == NULL) || (atts == NULL)) return; i = 0; att = atts[i++]; while (att != NULL) { value = atts[i++]; if (value != NULL) { if ((!xmlStrcasecmp(att, BAD_CAST "http-equiv")) && (!xmlStrcasecmp(value, BAD_CAST "Content-Type"))) { http = 1; } else if (!xmlStrcasecmp(att, BAD_CAST "charset")) { xmlChar *copy; copy = xmlStrdup(value); if (copy == NULL) htmlErrMemory(ctxt); xmlSetDeclaredEncoding(ctxt, copy); } else if (!xmlStrcasecmp(att, BAD_CAST "content")) { content = value; } } att = atts[i++]; } if ((http) && (content != NULL)) htmlCheckEncoding(ctxt, content); } /** * htmlParseStartTag: * @ctxt: an HTML parser context * * parse a start of tag either for rule element or * EmptyElement. In both case we don't parse the tag closing chars. * * [40] STag ::= '<' Name (S Attribute)* S? '>' * * [44] EmptyElemTag ::= '<' Name (S Attribute)* S? '/>' * * With namespace: * * [NS 8] STag ::= '<' QName (S Attribute)* S? '>' * * [NS 10] EmptyElement ::= '<' QName (S Attribute)* S? '/>' * * Returns 0 in case of success, -1 in case of error and 1 if discarded */ static void htmlParseStartTag(htmlParserCtxtPtr ctxt) { const xmlChar *name; const xmlChar *attname; xmlChar *attvalue; const xmlChar **atts; int nbatts = 0; int maxatts; int meta = 0; int i; int discardtag = 0; SKIP(1); atts = ctxt->atts; maxatts = ctxt->maxatts; GROW; name = htmlParseHTMLName(ctxt, 0); if (name == NULL) return; if (xmlStrEqual(name, BAD_CAST"meta")) meta = 1; if ((ctxt->options & HTML_PARSE_HTML5) == 0) { /* * Check for auto-closure of HTML elements. */ htmlAutoClose(ctxt, name); /* * Check for implied HTML elements. */ htmlCheckImplied(ctxt, name); /* * Avoid html at any level > 0, head at any level != 1 * or any attempt to recurse body */ if ((ctxt->nameNr > 0) && (xmlStrEqual(name, BAD_CAST"html"))) { htmlParseErr(ctxt, XML_HTML_STRUCURE_ERROR, "htmlParseStartTag: misplaced tag\n", name, NULL); discardtag = 1; ctxt->depth++; } if ((ctxt->nameNr != 1) && (xmlStrEqual(name, BAD_CAST"head"))) { htmlParseErr(ctxt, XML_HTML_STRUCURE_ERROR, "htmlParseStartTag: misplaced
tag\n", name, NULL); discardtag = 1; ctxt->depth++; } if (xmlStrEqual(name, BAD_CAST"body")) { int indx; for (indx = 0;indx < ctxt->nameNr;indx++) { if (xmlStrEqual(ctxt->nameTab[indx], BAD_CAST"body")) { htmlParseErr(ctxt, XML_HTML_STRUCURE_ERROR, "htmlParseStartTag: misplaced tag\n", name, NULL); discardtag = 1; ctxt->depth++; } } } } /* * Now parse the attributes, it ends up with the ending * * (S Attribute)* S? */ SKIP_BLANKS; while ((ctxt->input->cur < ctxt->input->end) && (CUR != '>') && ((CUR != '/') || (NXT(1) != '>')) && (PARSER_STOPPED(ctxt) == 0)) { /* unexpected-solidus-in-tag */ if (CUR == '/') { NEXT; SKIP_BLANKS; continue; } GROW; attname = htmlParseAttribute(ctxt, &attvalue); if (attname != NULL) { /* * Well formedness requires at most one declaration of an attribute */ for (i = 0; i < nbatts;i += 2) { if (xmlStrEqual(atts[i], attname)) { if (attvalue != NULL) xmlFree(attvalue); goto failed; } } /* * Add the pair to atts */ if (atts == NULL) { maxatts = 22; /* allow for 10 attrs by default */ atts = (const xmlChar **) xmlMalloc(maxatts * sizeof(xmlChar *)); if (atts == NULL) { htmlErrMemory(ctxt); if (attvalue != NULL) xmlFree(attvalue); goto failed; } ctxt->atts = atts; ctxt->maxatts = maxatts; } else if (nbatts + 4 > maxatts) { const xmlChar **n; maxatts *= 2; n = (const xmlChar **) xmlRealloc((void *) atts, maxatts * sizeof(const xmlChar *)); if (n == NULL) { htmlErrMemory(ctxt); if (attvalue != NULL) xmlFree(attvalue); goto failed; } atts = n; ctxt->atts = atts; ctxt->maxatts = maxatts; } atts[nbatts++] = attname; atts[nbatts++] = attvalue; atts[nbatts] = NULL; atts[nbatts + 1] = NULL; } else { if (attvalue != NULL) xmlFree(attvalue); } failed: SKIP_BLANKS; } if (ctxt->input->cur >= ctxt->input->end) { discardtag = 1; goto done; } /* * Handle specific association to the META tag */ if (meta && (nbatts != 0)) htmlCheckMeta(ctxt, atts); /* * SAX: Start of Element ! */ if (!discardtag) { if (ctxt->options & HTML_PARSE_HTML5) { if (ctxt->nameNr > 0) htmlnamePop(ctxt); } htmlnamePush(ctxt, name); if ((ctxt->sax != NULL) && (ctxt->sax->startElement != NULL)) { if (nbatts != 0) ctxt->sax->startElement(ctxt->userData, name, atts); else ctxt->sax->startElement(ctxt->userData, name, NULL); } } done: if (atts != NULL) { for (i = 1;i < nbatts;i += 2) { if (atts[i] != NULL) xmlFree((xmlChar *) atts[i]); } } } /** * htmlParseEndTag: * @ctxt: an HTML parser context * * parse an end of tag * * [42] ETag ::= '' Name S? '>' * * With namespace * * [NS 9] ETag ::= '' QName S? '>' * * Returns 1 if the current level should be closed. */ static void htmlParseEndTag(htmlParserCtxtPtr ctxt) { const xmlChar *name; const xmlChar *oldname; int i; SKIP(2); if (CUR == '>') { SKIP(1); return; } if (!IS_ASCII_LETTER(CUR)) { htmlParseComment(ctxt, /* bogus */ 1); return; } name = htmlParseHTMLName(ctxt, 0); if (name == NULL) return; /* * Parse and ignore attributes. */ SKIP_BLANKS; while ((ctxt->input->cur < ctxt->input->end) && (CUR != '>') && ((CUR != '/') || (NXT(1) != '>')) && (ctxt->instate != XML_PARSER_EOF)) { xmlChar *attvalue = NULL; /* unexpected-solidus-in-tag */ if (CUR == '/') { NEXT; SKIP_BLANKS; continue; } GROW; htmlParseAttribute(ctxt, &attvalue); if (attvalue != NULL) xmlFree(attvalue); SKIP_BLANKS; } if (CUR == '>') { NEXT; } else if ((CUR == '/') && (NXT(1) == '>')) { SKIP(2); } else { return; } if (ctxt->options & HTML_PARSE_HTML5) { if ((ctxt->sax != NULL) && (ctxt->sax->endElement != NULL)) ctxt->sax->endElement(ctxt->userData, name); return; } /* * if we ignored misplaced tags in htmlParseStartTag don't pop them * out now. */ if ((ctxt->depth > 0) && (xmlStrEqual(name, BAD_CAST "html") || xmlStrEqual(name, BAD_CAST "body") || xmlStrEqual(name, BAD_CAST "head"))) { ctxt->depth--; return; } /* * If the name read is not one of the element in the parsing stack * then return, it's just an error. */ for (i = (ctxt->nameNr - 1); i >= 0; i--) { if (xmlStrEqual(name, ctxt->nameTab[i])) break; } if (i < 0) { htmlParseErr(ctxt, XML_ERR_TAG_NAME_MISMATCH, "Unexpected end tag : %s\n", name, NULL); return; } /* * Check for auto-closure of HTML elements. */ htmlAutoCloseOnClose(ctxt, name); /* * Well formedness constraints, opening and closing must match. * With the exception that the autoclose may have popped stuff out * of the stack. */ if ((ctxt->name != NULL) && (!xmlStrEqual(ctxt->name, name))) { htmlParseErr(ctxt, XML_ERR_TAG_NAME_MISMATCH, "Opening and ending tag mismatch: %s and %s\n", name, ctxt->name); } /* * SAX: End of Tag */ oldname = ctxt->name; if ((oldname != NULL) && (xmlStrEqual(oldname, name))) { htmlParserFinishElementParsing(ctxt); if ((ctxt->sax != NULL) && (ctxt->sax->endElement != NULL)) ctxt->sax->endElement(ctxt->userData, name); htmlnamePop(ctxt); } } /** * htmlParseReference: * @ctxt: an HTML parser context * * parse and handle entity references in content, * this will end-up in a call to character() since this is either a * CharRef, or a predefined entity. */ static void htmlParseReference(htmlParserCtxtPtr ctxt) { const xmlChar *repl = NULL; int replLen = 0; xmlChar out[6]; if ((NXT(1) == '#') && ((IS_ASCII_DIGIT(NXT(2))) || ((UPP(2) == 'X') && ((IS_ASCII_DIGIT(NXT(3))) || ((UPP(3) >= 'A') && (UPP(3) <= 'F')))))) { unsigned int c; int bits, i = 0; c = htmlParseCharRef(ctxt); if (c == 0) return; if (c < 0x80) { out[i++]= c; bits= -6; } else if (c < 0x800) { out[i++]=((c >> 6) & 0x1F) | 0xC0; bits= 0; } else if (c < 0x10000) { out[i++]=((c >> 12) & 0x0F) | 0xE0; bits= 6; } else { out[i++]=((c >> 18) & 0x07) | 0xF0; bits= 12; } for ( ; bits >= 0; bits-= 6) { out[i++]= ((c >> bits) & 0x3F) | 0x80; } out[i] = 0; repl = out; replLen = i; } else if (IS_ASCII_LETTER(NXT(1))) { int nameLen; repl = htmlFindEntityPrefix(CUR_PTR + 1, ctxt->input->end - CUR_PTR - 1, /* isAttr */ 0, &nameLen, &replLen); if (repl != NULL) SKIP(nameLen + 1); } if (repl == NULL) { repl = BAD_CAST "&"; replLen = 1; SKIP(1); } htmlCheckParagraph(ctxt); if ((ctxt->sax != NULL) && (ctxt->sax->characters != NULL)) ctxt->sax->characters(ctxt->userData, repl, replLen); } /** * htmlParseContent: * @ctxt: an HTML parser context * * Parse a content: comment, sub-element, reference or text. * New version for non recursive htmlParseElementInternal */ static void htmlParseContent(htmlParserCtxtPtr ctxt) { while ((PARSER_STOPPED(ctxt) == 0) && (ctxt->input->cur < ctxt->input->end)) { int mode; GROW; mode = ctxt->endCheckState; if ((mode == 0) && (CUR == '<')) { if (NXT(1) == '/') { htmlParseEndTag(ctxt); } else if (NXT(1) == '!') { /* * Sometimes DOCTYPE arrives in the middle of the document */ if ((UPP(2) == 'D') && (UPP(3) == 'O') && (UPP(4) == 'C') && (UPP(5) == 'T') && (UPP(6) == 'Y') && (UPP(7) == 'P') && (UPP(8) == 'E')) { htmlParseDocTypeDecl(ctxt); } else if ((NXT(2) == '-') && (NXT(3) == '-')) { SKIP(4); htmlParseComment(ctxt, /* bogus */ 0); } else { SKIP(2); htmlParseComment(ctxt, /* bogus */ 1); } } else if (NXT(1) == '?') { SKIP(1); htmlParseComment(ctxt, /* bogus */ 1); } else if (IS_ASCII_LETTER(NXT(1))) { htmlParseElementInternal(ctxt); } else { htmlCheckParagraph(ctxt); if ((ctxt->sax != NULL) && (!ctxt->disableSAX) && (ctxt->sax->characters != NULL)) ctxt->sax->characters(ctxt->userData, BAD_CAST "<", 1); SKIP(1); } } else if ((CUR == '&') && ((mode == 0) || (mode == DATA_RCDATA))) { htmlParseReference(ctxt); } else { htmlParseCharData(ctxt, /* terminate */ 1); } SHRINK; GROW; } if (ctxt->input->cur >= ctxt->input->end) htmlAutoCloseOnEnd(ctxt); } /** * htmlParseElementInternal: * @ctxt: an HTML parser context * * parse an HTML element, new version, non recursive * * [39] element ::= EmptyElemTag | STag content ETag * * [41] Attribute ::= Name Eq AttValue */ static int htmlParseElementInternal(htmlParserCtxtPtr ctxt) { const xmlChar *name; const htmlElemDesc * info; htmlParserNodeInfo node_info = { NULL, 0, 0, 0, 0 }; if ((ctxt == NULL) || (ctxt->input == NULL)) return(0); /* Capture start position */ if (ctxt->record_info) { node_info.begin_pos = ctxt->input->consumed + (CUR_PTR - ctxt->input->base); node_info.begin_line = ctxt->input->line; } htmlParseStartTag(ctxt); name = ctxt->name; if (name == NULL) return(0); /* * Lookup the info for that element. */ info = htmlTagLookup(name); if (info != NULL) ctxt->endCheckState = info->dataMode; if (ctxt->record_info) htmlNodeInfoPush(ctxt, &node_info); /* * Check for an Empty Element labeled the XML/SGML way */ if ((CUR == '/') && (NXT(1) == '>')) { SKIP(2); htmlParserFinishElementParsing(ctxt); if ((ctxt->options & HTML_PARSE_HTML5) == 0) { if ((ctxt->sax != NULL) && (ctxt->sax->endElement != NULL)) ctxt->sax->endElement(ctxt->userData, name); } htmlnamePop(ctxt); return(0); } if (CUR != '>') return(0); SKIP(1); /* * Check for an Empty Element from DTD definition */ if ((info != NULL) && (info->empty)) { htmlParserFinishElementParsing(ctxt); if ((ctxt->options & HTML_PARSE_HTML5) == 0) { if ((ctxt->sax != NULL) && (ctxt->sax->endElement != NULL)) ctxt->sax->endElement(ctxt->userData, name); } htmlnamePop(ctxt); return(0); } return(1); } /** * htmlParseElement: * @ctxt: an HTML parser context * * DEPRECATED: Internal function, don't use. * * parse an HTML element, this is highly recursive * this is kept for compatibility with previous code versions * * [39] element ::= EmptyElemTag | STag content ETag * * [41] Attribute ::= Name Eq AttValue */ void htmlParseElement(htmlParserCtxtPtr ctxt) { const xmlChar *oldptr; int depth; if ((ctxt == NULL) || (ctxt->input == NULL)) return; if (htmlParseElementInternal(ctxt) == 0) return; /* * Parse the content of the element: */ depth = ctxt->nameNr; while (CUR != 0) { oldptr = ctxt->input->cur; htmlParseContent(ctxt); if (oldptr==ctxt->input->cur) break; if (ctxt->nameNr < depth) break; } if (CUR == 0) { htmlAutoCloseOnEnd(ctxt); } } xmlNodePtr htmlCtxtParseContentInternal(htmlParserCtxtPtr ctxt, xmlParserInputPtr input) { xmlNodePtr root; xmlNodePtr list = NULL; xmlChar *rootName = BAD_CAST "#root"; root = xmlNewDocNode(ctxt->myDoc, NULL, rootName, NULL); if (root == NULL) { htmlErrMemory(ctxt); return(NULL); } if (xmlPushInput(ctxt, input) < 0) { xmlFreeNode(root); return(NULL); } htmlnamePush(ctxt, rootName); nodePush(ctxt, root); htmlParseContent(ctxt); /* TODO: Use xmlCtxtIsCatastrophicError */ if (ctxt->errNo != XML_ERR_NO_MEMORY) { xmlNodePtr cur; /* * Unlink newly created node list. */ list = root->children; root->children = NULL; root->last = NULL; for (cur = list; cur != NULL; cur = cur->next) cur->parent = NULL; } nodePop(ctxt); htmlnamePop(ctxt); /* xmlPopInput would free the stream */ inputPop(ctxt); xmlFreeNode(root); return(list); } /** * htmlParseDocument: * @ctxt: an HTML parser context * * Parse an HTML document and invoke the SAX handlers. This is useful * if you're only interested in custom SAX callbacks. If you want a * document tree, use htmlCtxtParseDocument. * * Returns 0, -1 in case of error. */ int htmlParseDocument(htmlParserCtxtPtr ctxt) { xmlDtdPtr dtd; if ((ctxt == NULL) || (ctxt->input == NULL)) return(-1); if ((ctxt->sax) && (ctxt->sax->setDocumentLocator)) { ctxt->sax->setDocumentLocator(ctxt->userData, (xmlSAXLocator *) &xmlDefaultSAXLocator); } xmlDetectEncoding(ctxt); /* * This is wrong but matches long-standing behavior. In most cases, * a document starting with an XML declaration will specify UTF-8. */ if (((ctxt->input->flags & XML_INPUT_HAS_ENCODING) == 0) && (xmlStrncmp(ctxt->input->cur, BAD_CAST "sax) && (ctxt->sax->startDocument) && (!ctxt->disableSAX)) ctxt->sax->startDocument(ctxt->userData); /* * Parse possible comments and PIs before any content */ while (CUR == '<') { if ((NXT(1) == '!') && (NXT(2) == '-') && (NXT(3) == '-')) { SKIP(4); htmlParseComment(ctxt, /* bogus */ 0); } else if (NXT(1) == '?') { SKIP(1); htmlParseComment(ctxt, /* bogus */ 1); } else { break; } SKIP_BLANKS; } /* * Then possibly doc type declaration(s) and more Misc * (doctypedecl Misc*)? */ if ((CUR == '<') && (NXT(1) == '!') && (UPP(2) == 'D') && (UPP(3) == 'O') && (UPP(4) == 'C') && (UPP(5) == 'T') && (UPP(6) == 'Y') && (UPP(7) == 'P') && (UPP(8) == 'E')) { htmlParseDocTypeDecl(ctxt); } SKIP_BLANKS; /* * Parse possible comments and PIs before any content */ while (CUR == '<') { if ((NXT(1) == '!') && (NXT(2) == '-') && (NXT(3) == '-')) { SKIP(4); htmlParseComment(ctxt, /* bogus */ 0); } else if (NXT(1) == '?') { SKIP(1); htmlParseComment(ctxt, /* bogus */ 1); } else { break; } SKIP_BLANKS; } /* * Time to start parsing the tree itself */ htmlParseContent(ctxt); /* * autoclose */ if (CUR == 0) htmlAutoCloseOnEnd(ctxt); /* * SAX: end of the document processing. */ if ((ctxt->sax) && (ctxt->sax->endDocument != NULL)) ctxt->sax->endDocument(ctxt->userData); if ((!(ctxt->options & HTML_PARSE_NODEFDTD)) && (ctxt->myDoc != NULL)) { dtd = xmlGetIntSubset(ctxt->myDoc); if (dtd == NULL) { ctxt->myDoc->intSubset = xmlCreateIntSubset(ctxt->myDoc, BAD_CAST "html", BAD_CAST "-//W3C//DTD HTML 4.0 Transitional//EN", BAD_CAST "http://www.w3.org/TR/REC-html40/loose.dtd"); if (ctxt->myDoc->intSubset == NULL) htmlErrMemory(ctxt); } } if (! ctxt->wellFormed) return(-1); return(0); } /************************************************************************ * * * Parser contexts handling * * * ************************************************************************/ /** * htmlInitParserCtxt: * @ctxt: an HTML parser context * @sax: SAX handler * @userData: user data * * Initialize a parser context * * Returns 0 in case of success and -1 in case of error */ static int htmlInitParserCtxt(htmlParserCtxtPtr ctxt, const htmlSAXHandler *sax, void *userData) { if (ctxt == NULL) return(-1); memset(ctxt, 0, sizeof(htmlParserCtxt)); ctxt->dict = xmlDictCreate(); if (ctxt->dict == NULL) return(-1); if (ctxt->sax == NULL) ctxt->sax = (htmlSAXHandler *) xmlMalloc(sizeof(htmlSAXHandler)); if (ctxt->sax == NULL) return(-1); if (sax == NULL) { memset(ctxt->sax, 0, sizeof(htmlSAXHandler)); xmlSAX2InitHtmlDefaultSAXHandler(ctxt->sax); ctxt->userData = ctxt; } else { memcpy(ctxt->sax, sax, sizeof(htmlSAXHandler)); ctxt->userData = userData ? userData : ctxt; } /* Allocate the Input stack */ ctxt->inputTab = (htmlParserInputPtr *) xmlMalloc(5 * sizeof(htmlParserInputPtr)); if (ctxt->inputTab == NULL) return(-1); ctxt->inputNr = 0; ctxt->inputMax = 5; ctxt->input = NULL; ctxt->version = NULL; ctxt->encoding = NULL; ctxt->standalone = -1; ctxt->instate = XML_PARSER_START; /* Allocate the Node stack */ ctxt->nodeTab = (htmlNodePtr *) xmlMalloc(10 * sizeof(htmlNodePtr)); if (ctxt->nodeTab == NULL) return(-1); ctxt->nodeNr = 0; ctxt->nodeMax = 10; ctxt->node = NULL; /* Allocate the Name stack */ ctxt->nameTab = (const xmlChar **) xmlMalloc(10 * sizeof(xmlChar *)); if (ctxt->nameTab == NULL) return(-1); ctxt->nameNr = 0; ctxt->nameMax = 10; ctxt->name = NULL; ctxt->nodeInfoTab = NULL; ctxt->nodeInfoNr = 0; ctxt->nodeInfoMax = 0; ctxt->myDoc = NULL; ctxt->wellFormed = 1; ctxt->replaceEntities = 0; ctxt->linenumbers = xmlLineNumbersDefaultValue; ctxt->keepBlanks = xmlKeepBlanksDefaultValue; ctxt->html = 1; ctxt->vctxt.flags = XML_VCTXT_USE_PCTXT; ctxt->vctxt.userData = ctxt; ctxt->vctxt.error = xmlParserValidityError; ctxt->vctxt.warning = xmlParserValidityWarning; ctxt->record_info = 0; ctxt->validate = 0; ctxt->checkIndex = 0; ctxt->catalogs = NULL; xmlInitNodeInfoSeq(&ctxt->node_seq); return(0); } /** * htmlFreeParserCtxt: * @ctxt: an HTML parser context * * Free all the memory used by a parser context. However the parsed * document in ctxt->myDoc is not freed. */ void htmlFreeParserCtxt(htmlParserCtxtPtr ctxt) { xmlFreeParserCtxt(ctxt); } /** * htmlNewParserCtxt: * * Allocate and initialize a new HTML parser context. * * This can be used to parse HTML documents into DOM trees with * functions like xmlCtxtReadFile or xmlCtxtReadMemory. * * See htmlCtxtUseOptions for parser options. * * See xmlCtxtSetErrorHandler for advanced error handling. * * See htmlNewSAXParserCtxt for custom SAX parsers. * * Returns the htmlParserCtxtPtr or NULL in case of allocation error */ htmlParserCtxtPtr htmlNewParserCtxt(void) { return(htmlNewSAXParserCtxt(NULL, NULL)); } /** * htmlNewSAXParserCtxt: * @sax: SAX handler * @userData: user data * * Allocate and initialize a new HTML SAX parser context. If userData * is NULL, the parser context will be passed as user data. * * Available since 2.11.0. If you want support older versions, * it's best to invoke htmlNewParserCtxt and set ctxt->sax with * struct assignment. * * Also see htmlNewParserCtxt. * * Returns the htmlParserCtxtPtr or NULL in case of allocation error */ htmlParserCtxtPtr htmlNewSAXParserCtxt(const htmlSAXHandler *sax, void *userData) { xmlParserCtxtPtr ctxt; xmlInitParser(); ctxt = (xmlParserCtxtPtr) xmlMalloc(sizeof(xmlParserCtxt)); if (ctxt == NULL) return(NULL); memset(ctxt, 0, sizeof(xmlParserCtxt)); if (htmlInitParserCtxt(ctxt, sax, userData) < 0) { htmlFreeParserCtxt(ctxt); return(NULL); } return(ctxt); } static htmlParserCtxtPtr htmlCreateMemoryParserCtxtInternal(const char *url, const char *buffer, size_t size, const char *encoding) { xmlParserCtxtPtr ctxt; xmlParserInputPtr input; if (buffer == NULL) return(NULL); ctxt = htmlNewParserCtxt(); if (ctxt == NULL) return(NULL); input = xmlCtxtNewInputFromMemory(ctxt, url, buffer, size, encoding, 0); if (input == NULL) { xmlFreeParserCtxt(ctxt); return(NULL); } if (inputPush(ctxt, input) < 0) { xmlFreeInputStream(input); xmlFreeParserCtxt(ctxt); return(NULL); } return(ctxt); } /** * htmlCreateMemoryParserCtxt: * @buffer: a pointer to a char array * @size: the size of the array * * DEPRECATED: Use htmlNewParserCtxt and htmlCtxtReadMemory. * * Create a parser context for an HTML in-memory document. The input * buffer must not contain any terminating null bytes. * * Returns the new parser context or NULL */ htmlParserCtxtPtr htmlCreateMemoryParserCtxt(const char *buffer, int size) { if (size <= 0) return(NULL); return(htmlCreateMemoryParserCtxtInternal(NULL, buffer, size, NULL)); } /** * htmlCreateDocParserCtxt: * @str: a pointer to an array of xmlChar * @encoding: encoding (optional) * * Create a parser context for a null-terminated string. * * Returns the new parser context or NULL if a memory allocation failed. */ static htmlParserCtxtPtr htmlCreateDocParserCtxt(const xmlChar *str, const char *url, const char *encoding) { xmlParserCtxtPtr ctxt; xmlParserInputPtr input; if (str == NULL) return(NULL); ctxt = htmlNewParserCtxt(); if (ctxt == NULL) return(NULL); input = xmlCtxtNewInputFromString(ctxt, url, (const char *) str, encoding, 0); if (input == NULL) { xmlFreeParserCtxt(ctxt); return(NULL); } if (inputPush(ctxt, input) < 0) { xmlFreeInputStream(input); xmlFreeParserCtxt(ctxt); return(NULL); } return(ctxt); } #ifdef LIBXML_PUSH_ENABLED /************************************************************************ * * * Progressive parsing interfaces * * * ************************************************************************/ enum xmlLookupStates { LSTATE_TAG_NAME = 0, LSTATE_BEFORE_ATTR_NAME, LSTATE_ATTR_NAME, LSTATE_AFTER_ATTR_NAME, LSTATE_BEFORE_ATTR_VALUE, LSTATE_ATTR_VALUE_DQUOTED, LSTATE_ATTR_VALUE_SQUOTED, LSTATE_ATTR_VALUE_UNQUOTED }; /** * htmlParseLookupGt: * @ctxt: an HTML parser context * * Check whether there's enough data in the input buffer to finish parsing * a tag. This has to take quotes into account. */ static int htmlParseLookupGt(xmlParserCtxtPtr ctxt) { const xmlChar *cur; const xmlChar *end = ctxt->input->end; int state = ctxt->endCheckState; size_t index; if (ctxt->checkIndex == 0) cur = ctxt->input->cur + 2; /* Skip 'input->cur + ctxt->checkIndex; while (cur < end) { int c = *cur++; if (state != LSTATE_ATTR_VALUE_SQUOTED && state != LSTATE_ATTR_VALUE_DQUOTED) { if (c == '/' && state != LSTATE_BEFORE_ATTR_VALUE && state != LSTATE_ATTR_VALUE_UNQUOTED) { state = LSTATE_BEFORE_ATTR_NAME; continue; } else if (c == '>') { ctxt->checkIndex = 0; ctxt->endCheckState = 0; return(0); } } switch (state) { case LSTATE_TAG_NAME: if (IS_WS_HTML(c)) state = LSTATE_BEFORE_ATTR_NAME; break; case LSTATE_BEFORE_ATTR_NAME: if (!IS_WS_HTML(c)) state = LSTATE_ATTR_NAME; break; case LSTATE_ATTR_NAME: if (c == '=') state = LSTATE_BEFORE_ATTR_VALUE; else if (IS_WS_HTML(c)) state = LSTATE_AFTER_ATTR_NAME; break; case LSTATE_AFTER_ATTR_NAME: if (c == '=') state = LSTATE_BEFORE_ATTR_VALUE; else if (!IS_WS_HTML(c)) state = LSTATE_ATTR_NAME; break; case LSTATE_BEFORE_ATTR_VALUE: if (c == '"') state = LSTATE_ATTR_VALUE_DQUOTED; else if (c == '\'') state = LSTATE_ATTR_VALUE_SQUOTED; else if (!IS_WS_HTML(c)) state = LSTATE_ATTR_VALUE_UNQUOTED; break; case LSTATE_ATTR_VALUE_DQUOTED: if (c == '"') state = LSTATE_BEFORE_ATTR_NAME; break; case LSTATE_ATTR_VALUE_SQUOTED: if (c == '\'') state = LSTATE_BEFORE_ATTR_NAME; break; case LSTATE_ATTR_VALUE_UNQUOTED: if (IS_WS_HTML(c)) state = LSTATE_BEFORE_ATTR_NAME; break; } } index = cur - ctxt->input->cur; if (index > LONG_MAX) { ctxt->checkIndex = 0; ctxt->endCheckState = 0; return(0); } ctxt->checkIndex = index; ctxt->endCheckState = state; return(-1); } /** * htmlParseLookupString: * @ctxt: an XML parser context * @startDelta: delta to apply at the start * @str: string * @strLen: length of string * * Check whether the input buffer contains a string. */ static int htmlParseLookupString(xmlParserCtxtPtr ctxt, size_t startDelta, const char *str, size_t strLen, size_t extraLen) { const xmlChar *end = ctxt->input->end; const xmlChar *cur, *term; size_t index, rescan; int ret; if (ctxt->checkIndex == 0) { cur = ctxt->input->cur + startDelta; } else { cur = ctxt->input->cur + ctxt->checkIndex; } term = BAD_CAST strstr((const char *) cur, str); if ((term != NULL) && ((size_t) (ctxt->input->end - term) >= extraLen + 1)) { ctxt->checkIndex = 0; if (term - ctxt->input->cur > INT_MAX / 2) ret = INT_MAX / 2; else ret = term - ctxt->input->cur; return(ret); } /* Rescan (strLen + extraLen - 1) characters. */ rescan = strLen + extraLen - 1; if ((size_t) (end - cur) <= rescan) end = cur; else end -= rescan; index = end - ctxt->input->cur; if (index > INT_MAX / 2) { ctxt->checkIndex = 0; ret = INT_MAX / 2; } else { ctxt->checkIndex = index; ret = -1; } return(ret); } /** * htmlParseLookupCommentEnd: * @ctxt: an HTML parser context * * Try to find a comment end tag in the input stream * The search includes "-->" as well as WHATWG-recommended incorrectly-closed tags. * (See https://html.spec.whatwg.org/multipage/parsing.html#parse-error-incorrectly-closed-comment) * This function has a side effect of (possibly) incrementing ctxt->checkIndex * to avoid rescanning sequences of bytes, it DOES change the state of the * parser, do not use liberally. * * Returns the index to the current parsing point if the full sequence is available, -1 otherwise. */ static int htmlParseLookupCommentEnd(htmlParserCtxtPtr ctxt) { int mark = 0; int offset; while (1) { mark = htmlParseLookupString(ctxt, 2, "--", 2, 0); if (mark < 0) break; if ((NXT(mark+2) == '>') || ((NXT(mark+2) == '!') && (NXT(mark+3) == '>'))) { ctxt->checkIndex = 0; break; } offset = (NXT(mark+2) == '!') ? 3 : 2; if (mark + offset >= ctxt->input->end - ctxt->input->cur) { ctxt->checkIndex = mark; return(-1); } ctxt->checkIndex = mark + 1; } return mark; } /** * htmlParseTryOrFinish: * @ctxt: an HTML parser context * @terminate: last chunk indicator * * Try to progress on parsing * * Returns zero if no parsing was possible */ static int htmlParseTryOrFinish(htmlParserCtxtPtr ctxt, int terminate) { int ret = 0; htmlParserInputPtr in; ptrdiff_t avail = 0; int cur; htmlParserNodeInfo node_info; while (PARSER_STOPPED(ctxt) == 0) { in = ctxt->input; if (in == NULL) break; avail = in->end - in->cur; if ((avail == 0) && (terminate)) { htmlAutoCloseOnEnd(ctxt); if ((ctxt->nameNr == 0) && (ctxt->instate != XML_PARSER_EOF)) { /* * SAX: end of the document processing. */ ctxt->instate = XML_PARSER_EOF; if ((ctxt->sax) && (ctxt->sax->endDocument != NULL)) ctxt->sax->endDocument(ctxt->userData); } } if (avail < 1) goto done; cur = in->cur[0]; switch (ctxt->instate) { case XML_PARSER_EOF: /* * Document parsing is done ! */ goto done; case XML_PARSER_START: /* * This is wrong but matches long-standing behavior. In most * cases, a document starting with an XML declaration will * specify UTF-8. */ if (((ctxt->input->flags & XML_INPUT_HAS_ENCODING) == 0) && (xmlStrncmp(ctxt->input->cur, BAD_CAST "sax) && (ctxt->sax->setDocumentLocator)) { ctxt->sax->setDocumentLocator(ctxt->userData, (xmlSAXLocator *) &xmlDefaultSAXLocator); } if ((ctxt->sax) && (ctxt->sax->startDocument) && (!ctxt->disableSAX)) ctxt->sax->startDocument(ctxt->userData); /* Allow callback to modify state */ if (ctxt->instate == XML_PARSER_START) ctxt->instate = XML_PARSER_MISC; break; case XML_PARSER_START_TAG: { const xmlChar *name; int next; const htmlElemDesc * info; /* * not enough chars in buffer */ if (avail < 2) goto done; cur = in->cur[0]; next = in->cur[1]; if (cur != '<') { ctxt->instate = XML_PARSER_CONTENT; break; } if (next == '/') { ctxt->instate = XML_PARSER_END_TAG; ctxt->checkIndex = 0; break; } if ((!terminate) && (htmlParseLookupGt(ctxt) < 0)) goto done; /* Capture start position */ if (ctxt->record_info) { node_info.begin_pos = ctxt->input->consumed + (CUR_PTR - ctxt->input->base); node_info.begin_line = ctxt->input->line; } htmlParseStartTag(ctxt); name = ctxt->name; if (name == NULL) break; /* * Lookup the info for that element. */ info = htmlTagLookup(name); if (info != NULL) ctxt->endCheckState = info->dataMode; /* * Check for an Empty Element labeled the XML/SGML way */ if ((CUR == '/') && (NXT(1) == '>')) { SKIP(2); htmlParserFinishElementParsing(ctxt); if ((ctxt->options & HTML_PARSE_HTML5) == 0) { if ((ctxt->sax != NULL) && (ctxt->sax->endElement != NULL)) ctxt->sax->endElement(ctxt->userData, name); } htmlnamePop(ctxt); ctxt->instate = XML_PARSER_CONTENT; break; } if (CUR != '>') break; SKIP(1); /* * Check for an Empty Element from DTD definition */ if ((info != NULL) && (info->empty)) { htmlParserFinishElementParsing(ctxt); if ((ctxt->options & HTML_PARSE_HTML5) == 0) { if ((ctxt->sax != NULL) && (ctxt->sax->endElement != NULL)) ctxt->sax->endElement(ctxt->userData, name); } htmlnamePop(ctxt); } if (ctxt->record_info) htmlNodeInfoPush(ctxt, &node_info); ctxt->instate = XML_PARSER_CONTENT; break; } case XML_PARSER_MISC: case XML_PARSER_PROLOG: case XML_PARSER_CONTENT: case XML_PARSER_EPILOG: { int mode; if ((ctxt->instate == XML_PARSER_MISC) || (ctxt->instate == XML_PARSER_PROLOG)) { SKIP_BLANKS; avail = in->end - in->cur; } if (avail < 1) goto done; cur = in->cur[0]; mode = ctxt->endCheckState; if (mode != 0) { int done = 0; while ((PARSER_STOPPED(ctxt) == 0) && (!done) && (in->cur < in->end)) { size_t extra; extra = strlen((const char *) ctxt->name) + 2; if ((!terminate) && (htmlParseLookupString(ctxt, 0, "<", 1, extra) < 0)) goto done; ctxt->checkIndex = 0; if ((cur == '&') && (mode == DATA_RCDATA)) { htmlParseReference(ctxt); } else { done = htmlParseCharData(ctxt, terminate); } cur = in->cur[0]; } break; } else if (cur == '<') { int next; if (avail < 2) { if (!terminate) goto done; next = ' '; } else { next = in->cur[1]; } if (next == '!') { if ((!terminate) && (avail < 4)) goto done; if ((in->cur[2] == '-') && (in->cur[3] == '-')) { if ((!terminate) && (htmlParseLookupCommentEnd(ctxt) < 0)) goto done; SKIP(4); htmlParseComment(ctxt, /* bogus */ 0); break; } if ((!terminate) && (avail < 9)) goto done; if ((UPP(2) == 'D') && (UPP(3) == 'O') && (UPP(4) == 'C') && (UPP(5) == 'T') && (UPP(6) == 'Y') && (UPP(7) == 'P') && (UPP(8) == 'E')) { if ((!terminate) && (htmlParseLookupString(ctxt, 9, ">", 1, 0) < 0)) goto done; htmlParseDocTypeDecl(ctxt); if (ctxt->instate == XML_PARSER_MISC) ctxt->instate = XML_PARSER_PROLOG; } else { if ((!terminate) && (htmlParseLookupString(ctxt, 2, ">", 1, 0) < 0)) goto done; SKIP(2); htmlParseComment(ctxt, /* bogus */ 1); } } else if (next == '?') { if ((!terminate) && (htmlParseLookupString(ctxt, 2, ">", 1, 0) < 0)) goto done; SKIP(1); htmlParseComment(ctxt, /* bogus */ 1); } else if (next == '/') { ctxt->instate = XML_PARSER_END_TAG; ctxt->checkIndex = 0; break; } else if (IS_ASCII_LETTER(next)) { if ((!terminate) && (next == 0)) goto done; ctxt->instate = XML_PARSER_START_TAG; ctxt->checkIndex = 0; break; } else { ctxt->instate = XML_PARSER_CONTENT; htmlCheckParagraph(ctxt); if ((ctxt->sax != NULL) && (!ctxt->disableSAX) && (ctxt->sax->characters != NULL)) ctxt->sax->characters(ctxt->userData, BAD_CAST "<", 1); SKIP(1); } } else { /* * check that the text sequence is complete * before handing out the data to the parser * to avoid problems with erroneous end of * data detection. */ if ((!terminate) && (htmlParseLookupString(ctxt, 0, "<", 1, 0) < 0)) goto done; ctxt->checkIndex = 0; while ((PARSER_STOPPED(ctxt) == 0) && (cur != '<') && (in->cur < in->end)) { if (cur == '&') { htmlParseReference(ctxt); } else { htmlParseCharData(ctxt, terminate); } cur = in->cur[0]; } } break; } case XML_PARSER_END_TAG: if ((terminate) && (avail == 2)) { htmlCheckParagraph(ctxt); if ((ctxt->sax != NULL) && (!ctxt->disableSAX) && (ctxt->sax->characters != NULL)) ctxt->sax->characters(ctxt->userData, BAD_CAST "", 2); goto done; } if ((!terminate) && (htmlParseLookupGt(ctxt) < 0)) goto done; htmlParseEndTag(ctxt); if (ctxt->nameNr == 0) { ctxt->instate = XML_PARSER_EPILOG; } else { ctxt->instate = XML_PARSER_CONTENT; } ctxt->checkIndex = 0; break; default: htmlParseErr(ctxt, XML_ERR_INTERNAL_ERROR, "HPP: internal error\n", NULL, NULL); ctxt->instate = XML_PARSER_EOF; break; } } done: if ((avail == 0) && (terminate)) { htmlAutoCloseOnEnd(ctxt); if ((ctxt->nameNr == 0) && (ctxt->instate != XML_PARSER_EOF)) { /* * SAX: end of the document processing. */ ctxt->instate = XML_PARSER_EOF; if ((ctxt->sax) && (ctxt->sax->endDocument != NULL)) ctxt->sax->endDocument(ctxt->userData); } } if ((!(ctxt->options & HTML_PARSE_NODEFDTD)) && (ctxt->myDoc != NULL) && ((terminate) || (ctxt->instate == XML_PARSER_EOF) || (ctxt->instate == XML_PARSER_EPILOG))) { xmlDtdPtr dtd; dtd = xmlGetIntSubset(ctxt->myDoc); if (dtd == NULL) { ctxt->myDoc->intSubset = xmlCreateIntSubset(ctxt->myDoc, BAD_CAST "html", BAD_CAST "-//W3C//DTD HTML 4.0 Transitional//EN", BAD_CAST "http://www.w3.org/TR/REC-html40/loose.dtd"); if (ctxt->myDoc->intSubset == NULL) htmlErrMemory(ctxt); } } return(ret); } /** * htmlParseChunk: * @ctxt: an HTML parser context * @chunk: chunk of memory * @size: size of chunk in bytes * @terminate: last chunk indicator * * Parse a chunk of memory in push parser mode. * * Assumes that the parser context was initialized with * htmlCreatePushParserCtxt. * * The last chunk, which will often be empty, must be marked with * the @terminate flag. With the default SAX callbacks, the resulting * document will be available in ctxt->myDoc. This pointer will not * be freed by the library. * * If the document isn't well-formed, ctxt->myDoc is set to NULL. * * Returns an xmlParserErrors code (0 on success). */ int htmlParseChunk(htmlParserCtxtPtr ctxt, const char *chunk, int size, int terminate) { if ((ctxt == NULL) || (ctxt->input == NULL)) return(XML_ERR_ARGUMENT); if (PARSER_STOPPED(ctxt) != 0) return(ctxt->errNo); if ((size > 0) && (chunk != NULL) && (ctxt->input != NULL) && (ctxt->input->buf != NULL)) { size_t pos = ctxt->input->cur - ctxt->input->base; int res; res = xmlParserInputBufferPush(ctxt->input->buf, size, chunk); xmlBufUpdateInput(ctxt->input->buf->buffer, ctxt->input, pos); if (res < 0) { htmlParseErr(ctxt, ctxt->input->buf->error, "xmlParserInputBufferPush failed", NULL, NULL); xmlHaltParser(ctxt); return (ctxt->errNo); } } htmlParseTryOrFinish(ctxt, terminate); if (terminate) { if (ctxt->instate != XML_PARSER_EOF) { if ((ctxt->sax) && (ctxt->sax->endDocument != NULL)) ctxt->sax->endDocument(ctxt->userData); } ctxt->instate = XML_PARSER_EOF; } return((xmlParserErrors) ctxt->errNo); } /************************************************************************ * * * User entry points * * * ************************************************************************/ /** * htmlCreatePushParserCtxt: * @sax: a SAX handler (optional) * @user_data: The user data returned on SAX callbacks (optional) * @chunk: a pointer to an array of chars (optional) * @size: number of chars in the array * @filename: only used for error reporting (optional) * @enc: encoding (deprecated, pass XML_CHAR_ENCODING_NONE) * * Create a parser context for using the HTML parser in push mode. * * Returns the new parser context or NULL if a memory allocation * failed. */ htmlParserCtxtPtr htmlCreatePushParserCtxt(htmlSAXHandlerPtr sax, void *user_data, const char *chunk, int size, const char *filename, xmlCharEncoding enc) { htmlParserCtxtPtr ctxt; htmlParserInputPtr input; const char *encoding; ctxt = htmlNewSAXParserCtxt(sax, user_data); if (ctxt == NULL) return(NULL); encoding = xmlGetCharEncodingName(enc); input = xmlNewPushInput(filename, chunk, size); if (input == NULL) { htmlFreeParserCtxt(ctxt); return(NULL); } if (inputPush(ctxt, input) < 0) { xmlFreeInputStream(input); xmlFreeParserCtxt(ctxt); return(NULL); } if (encoding != NULL) xmlSwitchEncodingName(ctxt, encoding); return(ctxt); } #endif /* LIBXML_PUSH_ENABLED */ /** * htmlSAXParseDoc: * @cur: a pointer to an array of xmlChar * @encoding: a free form C string describing the HTML document encoding, or NULL * @sax: the SAX handler block * @userData: if using SAX, this pointer will be provided on callbacks. * * DEPRECATED: Use htmlNewSAXParserCtxt and htmlCtxtReadDoc. * * Parse an HTML in-memory document. If sax is not NULL, use the SAX callbacks * to handle parse events. If sax is NULL, fallback to the default DOM * behavior and return a tree. * * Returns the resulting document tree unless SAX is NULL or the document is * not well formed. */ htmlDocPtr htmlSAXParseDoc(const xmlChar *cur, const char *encoding, htmlSAXHandlerPtr sax, void *userData) { htmlDocPtr ret; htmlParserCtxtPtr ctxt; if (cur == NULL) return(NULL); ctxt = htmlCreateDocParserCtxt(cur, NULL, encoding); if (ctxt == NULL) return(NULL); if (sax != NULL) { *ctxt->sax = *sax; ctxt->userData = userData; } htmlParseDocument(ctxt); ret = ctxt->myDoc; htmlFreeParserCtxt(ctxt); return(ret); } /** * htmlParseDoc: * @cur: a pointer to an array of xmlChar * @encoding: the encoding (optional) * * DEPRECATED: Use htmlReadDoc. * * Parse an HTML in-memory document and build a tree. * * This function uses deprecated global parser options. * * Returns the resulting document tree */ htmlDocPtr htmlParseDoc(const xmlChar *cur, const char *encoding) { return(htmlSAXParseDoc(cur, encoding, NULL, NULL)); } /** * htmlCreateFileParserCtxt: * @filename: the filename * @encoding: optional encoding * * DEPRECATED: Use htmlNewParserCtxt and htmlCtxtReadFile. * * Create a parser context to read from a file. * * A non-NULL encoding overrides encoding declarations in the document. * * Automatic support for ZLIB/Compress compressed document is provided * by default if found at compile-time. * * Returns the new parser context or NULL if a memory allocation failed. */ htmlParserCtxtPtr htmlCreateFileParserCtxt(const char *filename, const char *encoding) { htmlParserCtxtPtr ctxt; htmlParserInputPtr input; if (filename == NULL) return(NULL); ctxt = htmlNewParserCtxt(); if (ctxt == NULL) { return(NULL); } input = xmlCtxtNewInputFromUrl(ctxt, filename, NULL, encoding, 0); if (input == NULL) { xmlFreeParserCtxt(ctxt); return(NULL); } if (inputPush(ctxt, input) < 0) { xmlFreeInputStream(input); xmlFreeParserCtxt(ctxt); return(NULL); } return(ctxt); } /** * htmlSAXParseFile: * @filename: the filename * @encoding: encoding (optional) * @sax: the SAX handler block * @userData: if using SAX, this pointer will be provided on callbacks. * * DEPRECATED: Use htmlNewSAXParserCtxt and htmlCtxtReadFile. * * parse an HTML file and build a tree. Automatic support for ZLIB/Compress * compressed document is provided by default if found at compile-time. * It use the given SAX function block to handle the parsing callback. * If sax is NULL, fallback to the default DOM tree building routines. * * Returns the resulting document tree unless SAX is NULL or the document is * not well formed. */ htmlDocPtr htmlSAXParseFile(const char *filename, const char *encoding, htmlSAXHandlerPtr sax, void *userData) { htmlDocPtr ret; htmlParserCtxtPtr ctxt; htmlSAXHandlerPtr oldsax = NULL; ctxt = htmlCreateFileParserCtxt(filename, encoding); if (ctxt == NULL) return(NULL); if (sax != NULL) { oldsax = ctxt->sax; ctxt->sax = sax; ctxt->userData = userData; } htmlParseDocument(ctxt); ret = ctxt->myDoc; if (sax != NULL) { ctxt->sax = oldsax; ctxt->userData = NULL; } htmlFreeParserCtxt(ctxt); return(ret); } /** * htmlParseFile: * @filename: the filename * @encoding: encoding (optional) * * Parse an HTML file and build a tree. * * Returns the resulting document tree */ htmlDocPtr htmlParseFile(const char *filename, const char *encoding) { return(htmlSAXParseFile(filename, encoding, NULL, NULL)); } /** * htmlHandleOmittedElem: * @val: int 0 or 1 * * DEPRECATED: Use HTML_PARSE_NOIMPLIED * * Set and return the previous value for handling HTML omitted tags. * * Returns the last value for 0 for no handling, 1 for auto insertion. */ int htmlHandleOmittedElem(int val) { int old = htmlOmittedDefaultValue; htmlOmittedDefaultValue = val; return(old); } /** * htmlElementAllowedHere: * @parent: HTML parent element * @elt: HTML element * * Checks whether an HTML element may be a direct child of a parent element. * Note - doesn't check for deprecated elements * * Returns 1 if allowed; 0 otherwise. */ int htmlElementAllowedHere(const htmlElemDesc* parent, const xmlChar* elt) { const char** p ; if ( ! elt || ! parent || ! parent->subelts ) return 0 ; for ( p = parent->subelts; *p; ++p ) if ( !xmlStrcmp((const xmlChar *)*p, elt) ) return 1 ; return 0 ; } /** * htmlElementStatusHere: * @parent: HTML parent element * @elt: HTML element * * Checks whether an HTML element may be a direct child of a parent element. * and if so whether it is valid or deprecated. * * Returns one of HTML_VALID, HTML_DEPRECATED, HTML_INVALID */ htmlStatus htmlElementStatusHere(const htmlElemDesc* parent, const htmlElemDesc* elt) { if ( ! parent || ! elt ) return HTML_INVALID ; if ( ! htmlElementAllowedHere(parent, (const xmlChar*) elt->name ) ) return HTML_INVALID ; return ( elt->dtd == 0 ) ? HTML_VALID : HTML_DEPRECATED ; } /** * htmlAttrAllowed: * @elt: HTML element * @attr: HTML attribute * @legacy: whether to allow deprecated attributes * * Checks whether an attribute is valid for an element * Has full knowledge of Required and Deprecated attributes * * Returns one of HTML_REQUIRED, HTML_VALID, HTML_DEPRECATED, HTML_INVALID */ htmlStatus htmlAttrAllowed(const htmlElemDesc* elt, const xmlChar* attr, int legacy) { const char** p ; if ( !elt || ! attr ) return HTML_INVALID ; if ( elt->attrs_req ) for ( p = elt->attrs_req; *p; ++p) if ( !xmlStrcmp((const xmlChar*)*p, attr) ) return HTML_REQUIRED ; if ( elt->attrs_opt ) for ( p = elt->attrs_opt; *p; ++p) if ( !xmlStrcmp((const xmlChar*)*p, attr) ) return HTML_VALID ; if ( legacy && elt->attrs_depr ) for ( p = elt->attrs_depr; *p; ++p) if ( !xmlStrcmp((const xmlChar*)*p, attr) ) return HTML_DEPRECATED ; return HTML_INVALID ; } /** * htmlNodeStatus: * @node: an htmlNodePtr in a tree * @legacy: whether to allow deprecated elements (YES is faster here * for Element nodes) * * Checks whether the tree node is valid. Experimental (the author * only uses the HTML enhancements in a SAX parser) * * Return: for Element nodes, a return from htmlElementAllowedHere (if * legacy allowed) or htmlElementStatusHere (otherwise). * for Attribute nodes, a return from htmlAttrAllowed * for other nodes, HTML_NA (no checks performed) */ htmlStatus htmlNodeStatus(htmlNodePtr node, int legacy) { if ( ! node ) return HTML_INVALID ; switch ( node->type ) { case XML_ELEMENT_NODE: return legacy ? ( htmlElementAllowedHere ( htmlTagLookup(node->parent->name) , node->name ) ? HTML_VALID : HTML_INVALID ) : htmlElementStatusHere( htmlTagLookup(node->parent->name) , htmlTagLookup(node->name) ) ; case XML_ATTRIBUTE_NODE: return htmlAttrAllowed( htmlTagLookup(node->parent->name) , node->name, legacy) ; default: return HTML_NA ; } } /************************************************************************ * * * New set (2.6.0) of simpler and more flexible APIs * * * ************************************************************************/ /** * DICT_FREE: * @str: a string * * Free a string if it is not owned by the "dict" dictionary in the * current scope */ #define DICT_FREE(str) \ if ((str) && ((!dict) || \ (xmlDictOwns(dict, (const xmlChar *)(str)) == 0))) \ xmlFree((char *)(str)); /** * htmlCtxtReset: * @ctxt: an HTML parser context * * Reset a parser context */ void htmlCtxtReset(htmlParserCtxtPtr ctxt) { xmlParserInputPtr input; xmlDictPtr dict; if (ctxt == NULL) return; dict = ctxt->dict; while ((input = inputPop(ctxt)) != NULL) { /* Non consuming */ xmlFreeInputStream(input); } ctxt->inputNr = 0; ctxt->input = NULL; ctxt->spaceNr = 0; if (ctxt->spaceTab != NULL) { ctxt->spaceTab[0] = -1; ctxt->space = &ctxt->spaceTab[0]; } else { ctxt->space = NULL; } ctxt->nodeNr = 0; ctxt->node = NULL; ctxt->nameNr = 0; ctxt->name = NULL; ctxt->nsNr = 0; DICT_FREE(ctxt->version); ctxt->version = NULL; DICT_FREE(ctxt->encoding); ctxt->encoding = NULL; DICT_FREE(ctxt->extSubURI); ctxt->extSubURI = NULL; DICT_FREE(ctxt->extSubSystem); ctxt->extSubSystem = NULL; if (ctxt->directory != NULL) { xmlFree(ctxt->directory); ctxt->directory = NULL; } if (ctxt->myDoc != NULL) xmlFreeDoc(ctxt->myDoc); ctxt->myDoc = NULL; ctxt->standalone = -1; ctxt->hasExternalSubset = 0; ctxt->hasPErefs = 0; ctxt->html = 1; ctxt->instate = XML_PARSER_START; ctxt->wellFormed = 1; ctxt->nsWellFormed = 1; ctxt->disableSAX = 0; ctxt->valid = 1; ctxt->vctxt.userData = ctxt; ctxt->vctxt.flags = XML_VCTXT_USE_PCTXT; ctxt->vctxt.error = xmlParserValidityError; ctxt->vctxt.warning = xmlParserValidityWarning; ctxt->record_info = 0; ctxt->checkIndex = 0; ctxt->endCheckState = 0; ctxt->inSubset = 0; ctxt->errNo = XML_ERR_OK; ctxt->depth = 0; ctxt->catalogs = NULL; xmlInitNodeInfoSeq(&ctxt->node_seq); if (ctxt->attsDefault != NULL) { xmlHashFree(ctxt->attsDefault, xmlHashDefaultDeallocator); ctxt->attsDefault = NULL; } if (ctxt->attsSpecial != NULL) { xmlHashFree(ctxt->attsSpecial, NULL); ctxt->attsSpecial = NULL; } ctxt->nbErrors = 0; ctxt->nbWarnings = 0; if (ctxt->lastError.code != XML_ERR_OK) xmlResetError(&ctxt->lastError); } /** * htmlCtxtUseOptions: * @ctxt: an HTML parser context * @options: a combination of htmlParserOption(s) * * Applies the options to the parser context * * Returns 0 in case of success, the set of unknown or unimplemented options * in case of error. */ int htmlCtxtUseOptions(htmlParserCtxtPtr ctxt, int options) { if (ctxt == NULL) return(-1); if (options & HTML_PARSE_NOWARNING) { ctxt->sax->warning = NULL; ctxt->vctxt.warning = NULL; options -= XML_PARSE_NOWARNING; ctxt->options |= XML_PARSE_NOWARNING; } if (options & HTML_PARSE_NOERROR) { ctxt->sax->error = NULL; ctxt->vctxt.error = NULL; ctxt->sax->fatalError = NULL; options -= XML_PARSE_NOERROR; ctxt->options |= XML_PARSE_NOERROR; } if (options & HTML_PARSE_PEDANTIC) { ctxt->pedantic = 1; options -= XML_PARSE_PEDANTIC; ctxt->options |= XML_PARSE_PEDANTIC; } else ctxt->pedantic = 0; if (options & XML_PARSE_NOBLANKS) { ctxt->keepBlanks = 0; ctxt->sax->ignorableWhitespace = xmlSAX2IgnorableWhitespace; options -= XML_PARSE_NOBLANKS; ctxt->options |= XML_PARSE_NOBLANKS; } else ctxt->keepBlanks = 1; if (options & HTML_PARSE_RECOVER) { ctxt->recovery = 1; options -= HTML_PARSE_RECOVER; } else ctxt->recovery = 0; if (options & HTML_PARSE_COMPACT) { ctxt->options |= HTML_PARSE_COMPACT; options -= HTML_PARSE_COMPACT; } if (options & XML_PARSE_HUGE) { ctxt->options |= XML_PARSE_HUGE; options -= XML_PARSE_HUGE; } if (options & HTML_PARSE_NODEFDTD) { ctxt->options |= HTML_PARSE_NODEFDTD; options -= HTML_PARSE_NODEFDTD; } if (options & HTML_PARSE_IGNORE_ENC) { ctxt->options |= HTML_PARSE_IGNORE_ENC; options -= HTML_PARSE_IGNORE_ENC; } if (options & HTML_PARSE_NOIMPLIED) { ctxt->options |= HTML_PARSE_NOIMPLIED; options -= HTML_PARSE_NOIMPLIED; } if (options & HTML_PARSE_HTML5) { ctxt->options |= HTML_PARSE_HTML5; options -= HTML_PARSE_HTML5; } ctxt->dictNames = 0; ctxt->linenumbers = 1; return (options); } /** * htmlCtxtParseDocument: * @ctxt: an HTML parser context * @input: parser input * * Parse an HTML document and return the resulting document tree. * * Available since 2.13.0. * * Returns the resulting document tree or NULL */ htmlDocPtr htmlCtxtParseDocument(htmlParserCtxtPtr ctxt, xmlParserInputPtr input) { htmlDocPtr ret; if ((ctxt == NULL) || (input == NULL)) return(NULL); /* assert(ctxt->inputNr == 0); */ while (ctxt->inputNr > 0) xmlFreeInputStream(inputPop(ctxt)); if (inputPush(ctxt, input) < 0) { xmlFreeInputStream(input); return(NULL); } ctxt->html = 1; htmlParseDocument(ctxt); if (ctxt->errNo != XML_ERR_NO_MEMORY) { ret = ctxt->myDoc; } else { ret = NULL; xmlFreeDoc(ctxt->myDoc); } ctxt->myDoc = NULL; /* assert(ctxt->inputNr == 1); */ while (ctxt->inputNr > 0) xmlFreeInputStream(inputPop(ctxt)); return(ret); } /** * htmlReadDoc: * @str: a pointer to a zero terminated string * @url: only used for error reporting (optoinal) * @encoding: the document encoding (optional) * @options: a combination of htmlParserOptions * * Convenience function to parse an HTML document from a zero-terminated * string. * * See htmlCtxtReadDoc for details. * * Returns the resulting document tree. */ htmlDocPtr htmlReadDoc(const xmlChar *str, const char *url, const char *encoding, int options) { htmlParserCtxtPtr ctxt; xmlParserInputPtr input; htmlDocPtr doc; ctxt = htmlNewParserCtxt(); if (ctxt == NULL) return(NULL); htmlCtxtUseOptions(ctxt, options); input = xmlCtxtNewInputFromString(ctxt, url, (const char *) str, encoding, XML_INPUT_BUF_STATIC); doc = htmlCtxtParseDocument(ctxt, input); htmlFreeParserCtxt(ctxt); return(doc); } /** * htmlReadFile: * @filename: a file or URL * @encoding: the document encoding (optional) * @options: a combination of htmlParserOptions * * Convenience function to parse an HTML file from the filesystem, * the network or a global user-defined resource loader. * * See htmlCtxtReadFile for details. * * Returns the resulting document tree. */ htmlDocPtr htmlReadFile(const char *filename, const char *encoding, int options) { htmlParserCtxtPtr ctxt; xmlParserInputPtr input; htmlDocPtr doc; ctxt = htmlNewParserCtxt(); if (ctxt == NULL) return(NULL); htmlCtxtUseOptions(ctxt, options); input = xmlCtxtNewInputFromUrl(ctxt, filename, NULL, encoding, 0); doc = htmlCtxtParseDocument(ctxt, input); htmlFreeParserCtxt(ctxt); return(doc); } /** * htmlReadMemory: * @buffer: a pointer to a char array * @size: the size of the array * @url: only used for error reporting (optional) * @encoding: the document encoding, or NULL * @options: a combination of htmlParserOption(s) * * Convenience function to parse an HTML document from memory. * The input buffer must not contain any terminating null bytes. * * See htmlCtxtReadMemory for details. * * Returns the resulting document tree */ htmlDocPtr htmlReadMemory(const char *buffer, int size, const char *url, const char *encoding, int options) { htmlParserCtxtPtr ctxt; xmlParserInputPtr input; htmlDocPtr doc; if (size < 0) return(NULL); ctxt = htmlNewParserCtxt(); if (ctxt == NULL) return(NULL); htmlCtxtUseOptions(ctxt, options); input = xmlCtxtNewInputFromMemory(ctxt, url, buffer, size, encoding, XML_INPUT_BUF_STATIC); doc = htmlCtxtParseDocument(ctxt, input); htmlFreeParserCtxt(ctxt); return(doc); } /** * htmlReadFd: * @fd: an open file descriptor * @url: only used for error reporting (optional) * @encoding: the document encoding, or NULL * @options: a combination of htmlParserOptions * * Convenience function to parse an HTML document from a * file descriptor. * * NOTE that the file descriptor will not be closed when the * context is freed or reset. * * See htmlCtxtReadFd for details. * * Returns the resulting document tree */ htmlDocPtr htmlReadFd(int fd, const char *url, const char *encoding, int options) { htmlParserCtxtPtr ctxt; xmlParserInputPtr input; htmlDocPtr doc; ctxt = htmlNewParserCtxt(); if (ctxt == NULL) return(NULL); htmlCtxtUseOptions(ctxt, options); input = xmlCtxtNewInputFromFd(ctxt, url, fd, encoding, 0); doc = htmlCtxtParseDocument(ctxt, input); htmlFreeParserCtxt(ctxt); return(doc); } /** * htmlReadIO: * @ioread: an I/O read function * @ioclose: an I/O close function (optional) * @ioctx: an I/O handler * @url: only used for error reporting (optional) * @encoding: the document encoding (optional) * @options: a combination of htmlParserOption(s) * * Convenience function to parse an HTML document from I/O functions * and context. * * See htmlCtxtReadIO for details. * * Returns the resulting document tree */ htmlDocPtr htmlReadIO(xmlInputReadCallback ioread, xmlInputCloseCallback ioclose, void *ioctx, const char *url, const char *encoding, int options) { htmlParserCtxtPtr ctxt; xmlParserInputPtr input; htmlDocPtr doc; ctxt = htmlNewParserCtxt(); if (ctxt == NULL) return (NULL); htmlCtxtUseOptions(ctxt, options); input = xmlCtxtNewInputFromIO(ctxt, url, ioread, ioclose, ioctx, encoding, 0); doc = htmlCtxtParseDocument(ctxt, input); htmlFreeParserCtxt(ctxt); return(doc); } /** * htmlCtxtReadDoc: * @ctxt: an HTML parser context * @str: a pointer to a zero terminated string * @URL: only used for error reporting (optional) * @encoding: the document encoding (optional) * @options: a combination of htmlParserOptions * * Parse an HTML in-memory document and build a tree. * * See htmlCtxtUseOptions for details. * * Returns the resulting document tree */ htmlDocPtr htmlCtxtReadDoc(htmlParserCtxtPtr ctxt, const xmlChar *str, const char *URL, const char *encoding, int options) { xmlParserInputPtr input; if (ctxt == NULL) return (NULL); htmlCtxtReset(ctxt); htmlCtxtUseOptions(ctxt, options); input = xmlCtxtNewInputFromString(ctxt, URL, (const char *) str, encoding, 0); return(htmlCtxtParseDocument(ctxt, input)); } /** * htmlCtxtReadFile: * @ctxt: an HTML parser context * @filename: a file or URL * @encoding: the document encoding (optional) * @options: a combination of htmlParserOptions * * Parse an HTML file from the filesystem, the network or a * user-defined resource loader. * * See htmlCtxtUseOptions for details. * * Returns the resulting document tree */ htmlDocPtr htmlCtxtReadFile(htmlParserCtxtPtr ctxt, const char *filename, const char *encoding, int options) { xmlParserInputPtr input; if (ctxt == NULL) return (NULL); htmlCtxtReset(ctxt); htmlCtxtUseOptions(ctxt, options); input = xmlCtxtNewInputFromUrl(ctxt, filename, NULL, encoding, 0); return(htmlCtxtParseDocument(ctxt, input)); } /** * htmlCtxtReadMemory: * @ctxt: an HTML parser context * @buffer: a pointer to a char array * @size: the size of the array * @URL: only used for error reporting (optional) * @encoding: the document encoding (optinal) * @options: a combination of htmlParserOptions * * Parse an HTML in-memory document and build a tree. The input buffer must * not contain any terminating null bytes. * * See htmlCtxtUseOptions for details. * * Returns the resulting document tree */ htmlDocPtr htmlCtxtReadMemory(htmlParserCtxtPtr ctxt, const char *buffer, int size, const char *URL, const char *encoding, int options) { xmlParserInputPtr input; if ((ctxt == NULL) || (size < 0)) return (NULL); htmlCtxtReset(ctxt); htmlCtxtUseOptions(ctxt, options); input = xmlCtxtNewInputFromMemory(ctxt, URL, buffer, size, encoding, XML_INPUT_BUF_STATIC); return(htmlCtxtParseDocument(ctxt, input)); } /** * htmlCtxtReadFd: * @ctxt: an HTML parser context * @fd: an open file descriptor * @URL: only used for error reporting (optional) * @encoding: the document encoding (optinal) * @options: a combination of htmlParserOptions * * Parse an HTML from a file descriptor and build a tree. * * See htmlCtxtUseOptions for details. * * NOTE that the file descriptor will not be closed when the * context is freed or reset. * * Returns the resulting document tree */ htmlDocPtr htmlCtxtReadFd(htmlParserCtxtPtr ctxt, int fd, const char *URL, const char *encoding, int options) { xmlParserInputPtr input; if (ctxt == NULL) return(NULL); htmlCtxtReset(ctxt); htmlCtxtUseOptions(ctxt, options); input = xmlCtxtNewInputFromFd(ctxt, URL, fd, encoding, 0); return(htmlCtxtParseDocument(ctxt, input)); } /** * htmlCtxtReadIO: * @ctxt: an HTML parser context * @ioread: an I/O read function * @ioclose: an I/O close function * @ioctx: an I/O handler * @URL: the base URL to use for the document * @encoding: the document encoding, or NULL * @options: a combination of htmlParserOption(s) * * Parse an HTML document from I/O functions and source and build a tree. * * See htmlCtxtUseOptions for details. * * Returns the resulting document tree */ htmlDocPtr htmlCtxtReadIO(htmlParserCtxtPtr ctxt, xmlInputReadCallback ioread, xmlInputCloseCallback ioclose, void *ioctx, const char *URL, const char *encoding, int options) { xmlParserInputPtr input; if (ctxt == NULL) return (NULL); htmlCtxtReset(ctxt); htmlCtxtUseOptions(ctxt, options); input = xmlCtxtNewInputFromIO(ctxt, URL, ioread, ioclose, ioctx, encoding, 0); return(htmlCtxtParseDocument(ctxt, input)); } #endif /* LIBXML_HTML_ENABLED */