210 lines
5.6 KiB
C
210 lines
5.6 KiB
C
/**
|
|
* Copyright (c) 2021 Dominic Masters
|
|
*
|
|
* This software is released under the MIT License.
|
|
* https://opensource.org/licenses/MIT
|
|
*/
|
|
|
|
#include "xml.h"
|
|
|
|
int32_t xmlLoadChild(xml_t *xml, char *data, int32_t i) {
|
|
char c;
|
|
int32_t level = 0;
|
|
uint8_t doing = XML_DOING_NOTHING;
|
|
bool insideTag = false;
|
|
char* buffer = malloc(sizeof(char) * XML_TEXT_BUFFER_MAX);
|
|
int32_t bufferLength = 0;
|
|
|
|
xml->value = NULL;
|
|
xml->attributeCount = 0;
|
|
|
|
xml->children = malloc(sizeof(xml_t) * XML_CHILD_COUNT_MAX);
|
|
xml->childrenCount = 0;
|
|
|
|
while(c = data[i++]) {
|
|
switch(doing) {
|
|
case XML_DOING_NOTHING:
|
|
// Look for either an opening tag (<) or a word for a value.
|
|
if(c == '>') continue;
|
|
if(c == '<') {
|
|
if(insideTag) {
|
|
i = xmlLoadChild(xml->children + xml->childrenCount++, data, i-1);
|
|
doing = XML_PARSING_CHILD;
|
|
} else {
|
|
doing = XML_PARSING_TAG_NAME;
|
|
level++;
|
|
insideTag = true;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if(xmlIsWhitespace(c)) continue;
|
|
doing = XML_PARSING_VALUE;
|
|
buffer[bufferLength++] = c;
|
|
break;
|
|
|
|
case XML_PARSING_TAG_NAME:
|
|
// Just keep reading until we either hit a space (end of the tag name)
|
|
// or a closing tag value, either / or >
|
|
if(xmlIsWhitespace(c) || c == '>' || c == '/') {
|
|
buffer[bufferLength] = '\0';
|
|
xml->node = buffer;
|
|
buffer = malloc(sizeof(char) * XML_TEXT_BUFFER_MAX);
|
|
bufferLength = 0;
|
|
if(c == '/') {
|
|
level--;
|
|
insideTag = false;
|
|
doing = XML_PARSING_CLOSE;
|
|
} else {
|
|
doing = c == '>' ? XML_DOING_NOTHING : XML_LOOKING_FOR_ATTRIBUTE;
|
|
}
|
|
continue;
|
|
}
|
|
buffer[bufferLength++] = c;
|
|
break;
|
|
|
|
case XML_LOOKING_FOR_ATTRIBUTE:
|
|
// Look until we hit either the end of a tag, or the attribute itself
|
|
if(xmlIsWhitespace(c) || c == '>' || c == '/' || c == '=') {
|
|
if(c == '>' || c == '/') {
|
|
doing = XML_DOING_NOTHING;
|
|
if(c == '/') {
|
|
level--;
|
|
insideTag = false;
|
|
doing = XML_PARSING_CLOSE;
|
|
}
|
|
} else if(c == '=') {
|
|
doing = XML_LOOKING_FOR_ATTRIBUTE_VALUE;
|
|
} else {
|
|
doing = XML_LOOKING_FOR_ATTRIBUTE;
|
|
}
|
|
|
|
if(bufferLength > 0) {
|
|
buffer[bufferLength] = '\0';
|
|
xml->attributeNames[xml->attributeCount++] = buffer;
|
|
xml->attributeDatas[xml->attributeCount] = NULL;
|
|
buffer = malloc(sizeof(char) * XML_TEXT_BUFFER_MAX);
|
|
bufferLength = 0;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
buffer[bufferLength++] = c;
|
|
break;
|
|
|
|
case XML_LOOKING_FOR_ATTRIBUTE_VALUE:
|
|
// Keep looking until we find a quote mark
|
|
if(xmlIsWhitespace(c)) continue;
|
|
if(c == '>' || c == '/') {
|
|
doing = XML_DOING_NOTHING;
|
|
insideTag = false;
|
|
continue;
|
|
}
|
|
|
|
if(c != '"') continue;
|
|
doing = XML_PARSING_ATTRIBUTE_VALUE;
|
|
break;
|
|
|
|
case XML_PARSING_ATTRIBUTE_VALUE:
|
|
// Parse the attribute value until we find a quote mark.
|
|
if(c == '"') {
|
|
doing = XML_LOOKING_FOR_ATTRIBUTE;
|
|
buffer[bufferLength] = '\0';
|
|
xml->attributeDatas[xml->attributeCount - 1] = buffer;
|
|
buffer = malloc(sizeof(char) * XML_TEXT_BUFFER_MAX);
|
|
bufferLength = 0;
|
|
continue;
|
|
}
|
|
|
|
buffer[bufferLength++] = c;
|
|
break;
|
|
|
|
case XML_PARSING_VALUE:
|
|
// Keep parsing child until we find a < for an opening/closing tag.
|
|
if(c == '<') {
|
|
// In HTML Spec there could be a child here but not in XML spec.
|
|
doing = XML_PARSING_CLOSE;
|
|
buffer[bufferLength] = '\0';
|
|
bufferLength = 0;
|
|
xml->value = buffer;
|
|
buffer = malloc(sizeof(char) * XML_TEXT_BUFFER_MAX);
|
|
continue;
|
|
}
|
|
|
|
buffer[bufferLength++] = c;
|
|
break;
|
|
|
|
case XML_PARSING_CHILD:
|
|
if(c == '<') {
|
|
// Read ahead and confirm this is a close or not
|
|
if(data[i] == '/') {
|
|
doing = XML_PARSING_CLOSE;
|
|
continue;
|
|
}
|
|
|
|
// Likely another child.
|
|
i = xmlLoadChild(xml->children + xml->childrenCount++, data, i-1);
|
|
}
|
|
|
|
if(xmlIsWhitespace(c)) continue;
|
|
|
|
// In HTML Spec there's a chance for there to be a value here, but not
|
|
// in the XML spec.
|
|
break;
|
|
|
|
case XML_PARSING_CLOSE:
|
|
// Just keep parsing until the tag closer finishes.
|
|
if(c != '>') continue;
|
|
doing = XML_DOING_NOTHING;
|
|
|
|
//TODO: Return index or something?
|
|
free(buffer);
|
|
return i;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
free(buffer);
|
|
return i;
|
|
}
|
|
|
|
void xmlLoad(xml_t *xml, char *data) {
|
|
xmlLoadChild(xml, data, 0);
|
|
}
|
|
|
|
void xmlDispose(xml_t *xml) {
|
|
uint8_t i;
|
|
|
|
// Dispose children recursively
|
|
for(i = 0; i < xml->childrenCount; i++) {
|
|
xmlDispose(xml->children + i);
|
|
}
|
|
|
|
// Free children array.
|
|
free(xml->children);
|
|
|
|
// Dispose attributes
|
|
for(i = 0; i < xml->attributeCount; i++) {
|
|
free(xml->attributeNames[i]);
|
|
if((xml->attributeDatas + i) != NULL) {
|
|
free(xml->attributeDatas[i]);
|
|
}
|
|
}
|
|
|
|
free(xml->node);
|
|
if(xml-> value != NULL) free(xml->value);
|
|
}
|
|
|
|
int16_t xmlGetAttributeByName(xml_t *xml, char *name) {
|
|
int16_t i;
|
|
for(i = 0; i < xml->attributeCount; i++) {
|
|
if(strcmp(xml->attributeNames[i], name) == 0) return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
bool xmlIsWhitespace(char c) {
|
|
return c == ' ' || c == '\r' || c == '\n' || c == '\t';
|
|
} |