/* * xmlParser for picoC on Loxone * * Andrej Konkow 03/2026 v01 */ // Maximum number of xml elements/tags in pool. #define XML_MAX_ELEMENTS 750 // Maximum number of xml elements/tags in pool. #define XML_MAX_ATTRIBUTES_PER_ELEMENT 5 // Maximum length of char content for text, cdata etc. #define XML_MAX_TAG_CHAR_CONTENT 25 // Maximum values to be addressed for printing by the user // We have 13 numeric channels and 2 channels for string data output #define MAXCHANNELS 13+2 // "Points" to no element #define XML_NO_ELEMENT -1 #define XML_PARSER_ERROR -2 #define PICOXML_ERRORLEN 250 // maximum error string length #define INPUT_HTTP_SERVER getinputtext(0) #define INPUT_HTTP_PAGE getinputtext(1) #define INPUT_XML_QUERY getinputtext(2) #define INPUT_ITERATION_WAIT_SEC getinput(0) // Pseudovalue for identfying a "no param" #define STRING_NOPARAM -99999 enum xml_datatype { DT_NONE = 0, DT_INT = 1, DT_FLOAT = 2, DT_BOOL = 3, DT_STRING = 4 }; /* Holds information about a specified value */ struct xml_query { char *name; int arrayindex; int datatype; char *attribute[XML_MAX_ATTRIBUTES_PER_ELEMENT]; char *value[XML_MAX_ATTRIBUTES_PER_ELEMENT]; struct xml_query *oneDeeper; struct xml_query *parent; }; /* * Attribute data inside of a xml tag. */ struct xmlAttribute { char *name; char *value; }; struct picoXml { char *name; // tag name short sizeAttr; // size of attr-list struct xmlAttribute szAttr[XML_MAX_ATTRIBUTES_PER_ELEMENT]; // tag attributes with name/value char txt[XML_MAX_TAG_CHAR_CONTENT+1]; // tag character content, empty string if none, For safety one more with a terminating '\0' char isCDATA; // Flag for being CDATA data short off; // tag offset from start of parent tag character content short next; // next tag with same name in this section at this depth short sibling; // next tag with different name in same section and depth short child; // head of sub tag list, NULL if none short parent; // parent tag, NULL if current tag is root tag }; struct parserData { short rootElement; // Holds the root XML structure short current; short elementsInUse; char *xmlBeg; // beginning of xml string int gotError; // Flagged with 1 if a parsing error occurred char errorMsg[100]; // In case holds the errormsg char *errorAdd; // Additional error information pointing to inside of xml }; struct parserData parser; // "Allocated" pool of tags and attributes for usage in picoC as malloc is problematic static struct picoXml szAllElements[XML_MAX_ELEMENTS]; // Maps for making efficient decisions static char MapIsEOL[127]; static char MapIsWhitespace[127]; static char MapIsAlphabetic[127]; static char MapIsWsAndEq[127]; static char MapIsWsAndEqSlGr[127]; static char MapIsWsAndEqSQuoDQuo[127]; static char MapIsWsAndGr[127]; static char MapIsWsAndSlGr[127]; static char MapIsBrOBrOGr[127]; static char MapIsPrimitive[127]; /* Helperfunctions for initialization */ void initMapWithWhitespaces(char *map) { memset(map, 0, 127); map[0] = 1; map[' '] = 1; map['\n'] = 1; map['\r'] = 1; map['\v'] = 1; map['\f'] = 1; map['\t'] = 1; } void initMapIsWhitespace() { initMapWithWhitespaces(MapIsWhitespace); } void initMapIsWsAndEq() { initMapWithWhitespaces(MapIsWsAndEq); MapIsWsAndEq['='] = 1; } void initMapIsWsAndEqSlGr() { initMapWithWhitespaces(MapIsWsAndEqSlGr); MapIsWsAndEqSlGr['='] = 1; MapIsWsAndEqSlGr['/'] = 1; MapIsWsAndEqSlGr['>'] = 1; } void initMapIsWsAndEqSQuoDQuo() { initMapWithWhitespaces(MapIsWsAndEqSQuoDQuo); MapIsWsAndEqSQuoDQuo['='] = 1; MapIsWsAndEqSQuoDQuo['\''] = 1; MapIsWsAndEqSQuoDQuo['"'] = 1; } void initMapIsWsAndGr() { initMapWithWhitespaces(MapIsWsAndGr); MapIsWsAndGr['>'] = 1; } void initMapIsWsAndSlGr() { initMapWithWhitespaces(MapIsWsAndSlGr); MapIsWsAndSlGr['/'] = 1; MapIsWsAndSlGr['>'] = 1; } void initMapIsBrOBrOGr() { memset(MapIsBrOBrOGr, 0, 127); MapIsBrOBrOGr['['] = 1; MapIsBrOBrOGr[']'] = 1; MapIsBrOBrOGr['>'] = 1; } void initMapIsAlphabetic() { memset(MapIsAlphabetic, 0, 127); int i; for (i='a'; i<='z'; i++) MapIsAlphabetic[i] = 1; for (i='A'; i<='Z'; i++) MapIsAlphabetic[i] = 1; } void initMapIsEOL() { initMapWithWhitespaces(MapIsEOL); MapIsEOL[' '] = 0; } void initMapIsPrimitive() { int i; memset(MapIsPrimitive, 0, 127); for (i='a'; i<='z'; i++) MapIsPrimitive[i] = 1; for (i='A'; i<='Z'; i++) MapIsPrimitive[i] = 1; for (i='0'; i<='9'; i++) MapIsPrimitive[i] = 1; MapIsPrimitive['.'] = 1; MapIsPrimitive['-'] = 1; } void initMaps() { initMapIsWhitespace(); initMapIsWsAndEq(); initMapIsWsAndEqSlGr(); initMapIsWsAndEqSQuoDQuo(); initMapIsWsAndGr(); initMapIsWsAndSlGr(); initMapIsBrOBrOGr(); initMapIsAlphabetic(); initMapIsEOL(); initMapIsPrimitive(); } // Initialize immediately initMaps(); /* Initialize a xml_query element */ void initElement(struct xml_query *element, struct xml_query *parent, int dt) { memset(element, 0, sizeof(struct xml_query)); element->datatype = dt; element->parent = parent; } /* Helperfunctions for messaging output */ void messageParam (char *msg, int val) { messageParamTimed (msg, val, 1000); } void messageParamTimed(char *msg, int val, int sleepMs) { messageParamTimedChannel(msg, val, sleepMs, 0); } void messageParamTimedChannel(char *msg, int val, int sleepMs, int channel) { if (val == STRING_NOPARAM) setoutputtext(channel, msg); else { char szOutMsg[150]; sprintf(szOutMsg,"%s %d", msg, val); setoutputtext(channel, szOutMsg); } if (sleepMs > 0) sleep(sleepMs); } /* Print error messages and return an error code */ int picoXml_err (char *s, char *err) { return picoXml_errParam(s, err, "\n"); } int picoXml_errParam(char *s, char *err, char *param) { char fmt[PICOXML_ERRORLEN]; int len_err = 0, len_param = 0; if (err != NULL) len_err = strlen(err); if (param != NULL) len_param = strlen(param); memset(fmt, 0, PICOXML_ERRORLEN); if (len_err) strncpy(fmt, err, PICOXML_ERRORLEN); if (len_param) strncpy(&fmt[len_err], param, PICOXML_ERRORLEN-len_err); if (s != NULL) strncpy(&fmt[len_err+len_param], s, PICOXML_ERRORLEN-len_err-len_param); messageParamTimedChannel(fmt, STRING_NOPARAM, 1000, 1); return XML_PARSER_ERROR; } // Sets a parser error msg and returns NULL char *setParserErrorParamChar(char *errorMsg, char *errorAdd, char c) { sprintf(parser.errorMsg, "%s%c", errorMsg, c); return setParserErrorNoParam(parser.errorMsg, errorAdd); } // Sets a parser error msg and returns NULL char *setParserErrorNoParam(char *errorMsg, char *errorAdd) { parser.gotError = 1; parser.errorAdd = errorAdd; sprintf(parser.errorMsg, "%s", errorMsg); return NULL; } /* "Allocate" new element for usage. Returns pool index of new, initialized element Returns XML_NO_ELEMENT if pool is exhausted. */ int addNewElement() { int newIdx = parser.elementsInUse; if (newIdx == XML_MAX_ELEMENTS) { parser.gotError = 1; sprintf(parser.errorMsg, "Tag pool too small: %d. Increase param XML_MAX_ELEMENTS", XML_MAX_ELEMENTS); return XML_NO_ELEMENT; } parser.elementsInUse++; struct picoXml *theElement = &szAllElements[newIdx]; theElement->next = XML_NO_ELEMENT; theElement->sibling = XML_NO_ELEMENT; theElement->child = XML_NO_ELEMENT; theElement->parent = XML_NO_ELEMENT; theElement->isCDATA = 0; memset(theElement->txt, 0, XML_MAX_TAG_CHAR_CONTENT+1); return newIdx; } /* "Adds" an attribute to an element by taking it out of the pool. Returns NULL if pool is exhausted. */ struct xmlAttribute *addNewAttribute(struct picoXml *element, char *name, char *value) { if (element->sizeAttr == XML_MAX_ATTRIBUTES_PER_ELEMENT) { parser.gotError = 1; sprintf(parser.errorMsg, "Attribute pool too small: %d. Increase param XML_MAX_ATTRIBUTES_PER_ELEMENT", XML_MAX_ATTRIBUTES_PER_ELEMENT); return NULL; } struct xmlAttribute *newAttr = &element->szAttr[element->sizeAttr]; element->sizeAttr++; newAttr->name = name; newAttr->value = value; return newAttr; }; /* First initialization of parser data */ void initParser() { memset(&parser, 0, sizeof(struct parserData)); memset(szAllElements, 0, sizeof(struct picoXml) * XML_MAX_ELEMENTS); parser.rootElement = addNewElement(); parser.current = parser.rootElement; } /* Handle opening tag */ int picoXml_openTag(char *name, struct xmlAttribute *attrList, int sizeAttrList) { struct picoXml *element; struct xmlAttribute *attr; int i, xml = parser.current, child=0; element = &szAllElements[xml]; if (element->name != NULL) { picoXml_add_child(xml, name, strlen(element->txt), &child); xml = child; } else element->name = name; // first open tag if (xml == XML_NO_ELEMENT) return XML_PARSER_ERROR; parser.current = xml; if (sizeAttrList == 0) return 1; element = &szAllElements[xml]; for (i=0; iname, attr->value) == NULL) return XML_PARSER_ERROR; } return 1; } /* Handle characters between open and closing tag */ void picoXml_handleCharacters(char *s, int len, char t) { struct picoXml *element; int txtlen; if (parser.current == XML_NO_ELEMENT) return; element = &szAllElements[parser.current]; if (element->name == NULL || len == 0) return; s[len] = 0; txtlen = strlen(element->txt); if (txtlen < XML_MAX_TAG_CHAR_CONTENT) // add space limited char content strncpy(&element->txt[txtlen], s, XML_MAX_TAG_CHAR_CONTENT-txtlen); if (t == 'c') element->isCDATA = 1; } /* Handle closing tag */ int picoXml_close_tag(char *name, char *s) { if (parser.current == XML_NO_ELEMENT) return picoXml_errParam(s, "unexpected closing tag : ", name); struct picoXml *cur_element = &szAllElements[parser.current]; if (cur_element->name == NULL || strcmp(name, cur_element->name)) return picoXml_errParam(s, "unexpected closing tag : ", name); parser.current = cur_element->parent; return XML_NO_ELEMENT; } // parse the given xml string and return the root index void picoXml_parseString(char *s, int *root) { char q, e, *d; int cnt, len; len = strlen(s); if (len == 0) { *root = picoXml_err(NULL, "No XML to parse."); return; } parser.xmlBeg = s; e = s[len - 1]; s[len - 1] = 0; while (*s && *s != '<') // go to first tag s++; if (! *s) { *root = picoXml_err(s, "root tag missing"); return; } while (TRUE) { parser.gotError = 0; parser.errorAdd = NULL; s++; d = s; if (MapIsAlphabetic[*s] || *s == '_' || *s == ':') { if (parser.current == XML_NO_ELEMENT) { *root = picoXml_err(d, "markup outside of root element"); return; } s = handleNewTag(s, d, e); } else if (*s == '/') { s = handleClosingTag(s, e); } else if (! strncmp(s, "!--", 3)) { s = strstr(s + 3, "--"); if (s == NULL) { *root = picoXml_err(d, "unclosed