Added texture, assets, tools and texture loading.

This commit is contained in:
2022-10-20 21:50:52 -07:00
parent 80d6cba854
commit 043873cc2d
38 changed files with 1626 additions and 15 deletions

View File

@ -0,0 +1,7 @@
# Copyright (c) 2021 Dominic Msters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
add_subdirectory(display)
add_subdirectory(file)

View File

@ -0,0 +1,25 @@
# Copyright (c) 2021 Dominic Msters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
add_subdirectory(texturegen)
add_subdirectory(truetypegen)
# Texture Tool
function(tool_texture target in out)
add_custom_target(${target}
COMMAND texturegen "${DAWN_ASSETS_SOURCE_DIR}/${in}" "${DAWN_ASSETS_BUILD_DIR}/${out}"
COMMENT "Generating texture ${target} from ${in}"
DEPENDS texturegen
)
endfunction()
# TrueType Tool
function(tool_truetype target in out width height fontSize)
add_custom_target(${target}
COMMAND truetypegen "${in}" "${DAWN_ASSETS_BUILD_DIR}/${out}" "${width}" "${height}" "${fontSize}"
COMMENT "Generating truetype ${target} from ${in}"
DEPENDS truetypegen
)
endfunction()

View File

@ -0,0 +1,24 @@
# Copyright (c) 2021 Dominic Msters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Texture Build Tool
project(texturegen VERSION 1.0)
add_executable(texturegen)
target_sources(texturegen
PRIVATE
main.c
../../utils/file.c
../../utils/image.c
)
target_include_directories(texturegen
PUBLIC
${CMAKE_CURRENT_LIST_DIR}/../../
${CMAKE_CURRENT_LIST_DIR}
)
target_link_libraries(texturegen
PUBLIC
${LIBS_PLATFORM}
stb
)

View File

@ -0,0 +1,87 @@
/**
* Copyright (c) 2021 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "../../utils/common.h"
#include "../../utils/file.h"
#include "../../utils/image.h"
int main(int argc, char *argv[]) {
FILE *file;
char path[FILENAME_MAX + 1];
uint8_t headerBuffer[32];
char *in;
char *out;
int w, h, channels, headerBufferLength;
stbi_uc *dataImageRaw, dataIn;
float *dataImage;
size_t i, len;
if(argc != 3) {
printf("Invalid number of arguments\n");
return 1;
}
// Set up strings
in = argv[1];
out = argv[2];
// Normalize slashes
fileNormalizeSlashes(in);
fileNormalizeSlashes(out);
// Check the output doesn't already exist
sprintf(path, "%s.texture", out);
file = fopen(path, "rb");
if(file != NULL) {
fclose(file);
return 0;
}
// Read in original texture
file = fopen(in, "rb");
if(file == NULL) {
printf("Failed to open file!\n");
return 1;
}
dataImageRaw = stbi_load_from_file(file, &w, &h, &channels, STBI_rgb_alpha);
if(dataImageRaw == NULL) {
printf("Failed to load input texture!\n");
return 1;
}
fclose(file);
// Convert to floating points
len = STBI_rgb_alpha * w * h;
dataImage = malloc(sizeof(float) * len);
for(i = 0; i < len; i++) {
dataIn = dataImageRaw[i];
dataImage[i] = ((float)dataIn) / 255.0f;
}
stbi_image_free(dataImageRaw);
// Open output file
fileMkdirp(path);
file = fopen(path, "wb");
if(file == NULL) {
printf("Invalid texture file out!\n");
return 1;
}
// Write info
headerBufferLength = sprintf(headerBuffer, "%i|%i|", w, h);
fwrite(headerBuffer, sizeof(uint8_t), headerBufferLength, file);
// Write texture
fwrite(dataImage, sizeof(float), len, file);
// Cleanup
fclose(file);
free(dataImage);
return 0;
}

View File

@ -0,0 +1,24 @@
# Copyright (c) 2021 Dominic Msters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Texture Build Tool
project(truetypegen VERSION 1.0)
add_executable(truetypegen)
target_sources(truetypegen
PRIVATE
main.c
../../utils/file.c
../../utils/image.c
)
target_include_directories(truetypegen
PUBLIC
${CMAKE_CURRENT_LIST_DIR}/../../
${CMAKE_CURRENT_LIST_DIR}
)
target_link_libraries(truetypegen
PUBLIC
${LIBS_PLATFORM}
stb
)

View File

@ -0,0 +1,140 @@
/**
* Copyright (c) 2021 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "../../utils/common.h"
#include "../../utils/file.h"
#include "../../utils/image.h"
#ifndef STB_TRUETYPE_IMPLEMENTATION
#define STB_TRUETYPE_IMPLEMENTATION
#include <stb_truetype.h>
#endif
#define TRUETYPE_FIRST_CHAR 32
#define TRUETYPE_NUM_CHARS 96
int main(int argc, char *args[]) {
FILE *file;
char path[FILENAME_MAX + 1];
int32_t width, height, fontSize, textureSize;
/*
args0 - PATH
args1 - Input Filename (TTF file)
args2 - Output Filename
args3 - Width of the output texture (Resolution X)
args4 - Height of the output texture (Resolution Y)
args5 - Font size to draw
*/
if(argc != 6) {
printf("Invalid number of arguments\n");
return 1;
}
char *fileIn = args[1];
char *fileOut = args[2];
char *strWidth = args[3];
char *strHeight = args[4];
char *strFontSize = args[5];
// Normalize slashes
fileNormalizeSlashes(fileIn);
fileNormalizeSlashes(fileOut);
// Check the output doesn't already exist
sprintf(path, "%s.truetype", fileOut);
file = fopen(path, "rb");
if(file != NULL) {
fclose(file);
return 0;
}
width = atoi(strWidth);
if(width <= 0) {
printf("Width is invalid.\n");
return 1;
}
height = atoi(strHeight);
if(height <= 0) {
printf("Height is invalid.\n");
return 1;
}
fontSize = atoi(strFontSize);
if(fontSize <= 0) {
printf("Font size is invalid.\n");
return 1;
}
// Read in the TTF data
file = fopen(fileIn, "rb");
if(file == NULL) {
printf("Failed to open input TTF file.\n");
return 1;
}
// Seek to end, get length, seek back to start.
fseek(file, 0, SEEK_END);
size_t fileSize = ftell(file);
fseek(file, 0, SEEK_SET);
// Read in all data
char *ttfData = malloc(sizeof(char) * fileSize);
size_t readSize = fread(ttfData, 1, fileSize, file);
fclose(file);
if(readSize < fileSize) {
printf("Failed to read all data form TTF\n");
return 1;
}
// Create bitmap data. This is a single channel color (alpha).
textureSize = width * height;
stbi_uc *bitmapData = malloc(sizeof(stbi_uc) * textureSize);
stbtt_bakedchar characterData[TRUETYPE_NUM_CHARS];
// Now parse the TTF itself.
stbtt_BakeFontBitmap(
ttfData, 0, (float)fontSize, bitmapData,
width, height,
TRUETYPE_FIRST_CHAR, TRUETYPE_NUM_CHARS,
characterData
);
// Prepare output file for writing.
sprintf(path, "%s.truetype", fileOut);
fileMkdirp(path);
file = fopen(path, "wb");
if(file == NULL) {
printf("Failed to create output TTF file\n");
return 1;
}
// Now prepare output data.
char headerBuffer[64];
int32_t headerBufferLength = sprintf(
headerBuffer, "%i|%i|%i|", width, height, fontSize
);
fwrite(headerBuffer, sizeof(char), headerBufferLength, file);
// Write output pixels.
float outputPixels[4];
for(int32_t i = 0; i < textureSize; i++) {
outputPixels[0] = 1.0f;
outputPixels[1] = 1.0f;
outputPixels[2] = 1.0f;
outputPixels[3] = ((float)bitmapData[i]) / 255.0f;
fwrite(outputPixels, sizeof(float), 4, file);
}
// Now write output quads data.
fwrite(characterData, sizeof(stbtt_bakedchar), TRUETYPE_NUM_CHARS, file);
fclose(file);
free(bitmapData);
return 0;
}

View File

@ -0,0 +1,27 @@
# Copyright (c) 2022 Dominic Msters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
function(tool_copy target)
math(EXPR CARGSN "${ARGC} - 1")
set(LOOP_DEPENDENCIES)
foreach(index RANGE 1 ${CARGSN} 2)
math(EXPR indexnext "${index} + 1")
set(LOOP_TARGET "item_${target}_${index}")
LIST(GET ARGV ${index} from)
LIST(GET ARGV ${indexnext} to)
LIST(APPEND LOOP_DEPENDENCIES ${LOOP_TARGET})
add_custom_command(OUTPUT ${LOOP_TARGET}
COMMAND ${CMAKE_COMMAND} -E copy "${from}" "${ASSETS_BUILD_DIR}/${to}"
COMMENT "Copying ${from} => ${to}"
)
endforeach()
add_custom_target(${target}
DEPENDS ${LOOP_DEPENDENCIES}
COMMENT "Creating dependency set ${target}"
)
endfunction()

View File

@ -0,0 +1,13 @@
/**
* Copyright (c) 2021 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>

115
src/dawntools/utils/csv.c Normal file
View File

@ -0,0 +1,115 @@
/**
* Copyright (c) 2022 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "csv.h"
void csvParse(char *string, csv_t *csv) {
char c;
size_t i, j, length;
csvparsestate_t state;
int32_t rowCellCount;
length = strlen(string);
csv->buffer = malloc(sizeof(char) * length * 2);
csv->cellCounts = malloc(sizeof(int32_t) * CSV_ROW_COUNT_MAX);
csv->rows = malloc(sizeof(char *) * 32 * CSV_ROW_COUNT_MAX);
i = 0;
j = 0;
rowCellCount = 0;
csv->rowCount = 0;
state = CSV_PARSE_STATE_FIND_CELL;
while(i < length) {
c = string[i++];
// What are we doing
switch(state) {
case CSV_PARSE_STATE_FIND_CELL:
if(c == '"') {
state = CSV_PARSE_STATE_PARSE_CELL_WITH_QUOTES;
csv->rows[(csv->rowCount * CSV_ROW_COUNT_MAX) + rowCellCount] = csv->buffer + j;
rowCellCount++;
continue;
} else if(c == '\r' || c == '\n') {
// Newline (todo: is this a blank line?)
state = CSV_PARSE_STATE_LINE_END;
continue;
} else if(c == ',') {
csv->rows[(csv->rowCount * CSV_ROW_COUNT_MAX) + rowCellCount] = csv->buffer + j;
csv->buffer[j++] = '\0';
rowCellCount++;
continue;
} else {
state = CSV_PARSE_STATE_PARSE_CELL;
csv->rows[(csv->rowCount * CSV_ROW_COUNT_MAX) + rowCellCount] = csv->buffer + j;
csv->buffer[j++] = c;
rowCellCount++;
continue;
}
case CSV_PARSE_STATE_PARSE_CELL:
if(c == '\r' || c == '\n') {
state = CSV_PARSE_STATE_LINE_END;
csv->buffer[j++] = '\0';
continue;
} else if(c == ',') {
state = CSV_PARSE_STATE_FIND_CELL;
csv->buffer[j++] = '\0';
continue;
}
csv->buffer[j++] = c;
continue;
case CSV_PARSE_STATE_PARSE_CELL_WITH_QUOTES:
if((c == '\\' && string[i] == '"') || (c == '"' && string[i] == '"')) {
// Handle escaped quotes. I normally see [\"] but excel does [""] in
// most cases
csv->buffer[j++] = '"';
i++;
continue;
} else if(c == '"') {
// Handle end of quoted string
state = CSV_PARSE_STATE_FIND_CELL;
csv->buffer[j++] = '\0';
// Because we tend to do [",] at the end of a quoted cell, we do this
// to prevent [,,] cases being treated the same
if(string[i] == ',') i++;
continue;
}
// Normal character.
csv->buffer[j++] = c;
continue;
case CSV_PARSE_STATE_LINE_END:
// Skip blanks
if(c == '\r' || c == '\n') continue;
csv->cellCounts[csv->rowCount] = rowCellCount;
csv->rowCount++;
rowCellCount = 0;
state = CSV_PARSE_STATE_FIND_CELL;
i--;
continue;
default:
printf("Error occured during parse operation.");
free(NULL);
}
}
csv->buffer[j++] = '\0';
if(rowCellCount != 0) {
csv->cellCounts[csv->rowCount] = rowCellCount;
csv->rowCount++;
}
}
void csvDispose(csv_t *csv) {
free(csv->buffer);
free(csv->cellCounts);
free(csv->rows);
}

29
src/dawntools/utils/csv.h Normal file
View File

@ -0,0 +1,29 @@
/**
* Copyright (c) 2022 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "common.h"
#define CSV_ROW_COUNT_MAX 128
typedef enum {
CSV_PARSE_STATE_FIND_CELL,//0
CSV_PARSE_STATE_PARSE_CELL_WITH_QUOTES,
CSV_PARSE_STATE_PARSE_CELL,//2
CSV_PARSE_STATE_LINE_END
} csvparsestate_t;
typedef struct {
char *buffer;
char **rows;
int32_t rowCount;
int32_t *cellCounts;
} csv_t;
void csvParse(char *string, csv_t *csv);
void csvDispose(csv_t *csv);

195
src/dawntools/utils/file.c Normal file
View File

@ -0,0 +1,195 @@
/**
* Copyright (c) 2021 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "file.h"
void fileNormalizeSlashes(char *string) {
char c;
int i = 0;
while(c = string[i++]) {
if(c != '\\' && c != '/') continue;
string[i-1] = FILE_PATH_SEP;
}
}
void fileMkdirp(char *path) {
char buffer[FILENAME_MAX];
char c;
int i = 0;
bool inFile;
bool hasMore;
inFile = false;
hasMore = false;
while(c = path[i]) {
if((c == '\\' || c == '/') && i > 0) {
buffer[i] = '\0';
fileMkdir(buffer, 0755);
inFile = false;
hasMore = false;
buffer[i] = FILE_PATH_SEP;
i++;
continue;
}
if(c == '.') inFile = true;
hasMore = true;
buffer[i] = c;
i++;
}
if(!inFile && hasMore) {
buffer[i] = '\0';
fileMkdir(buffer, 0755);
}
}
size_t assetReadString(FILE *file, char *buffer) {
size_t length;
fseek(file, 0, SEEK_END);// Seek to the end
length = ftell(file);// Get our current position (the end)
fseek(file, 0, SEEK_SET);// Reset the seek
if(buffer == NULL) return length;
return fread(buffer, 1, length, file);// Read all the bytes
}
int32_t readAhead(
char *bufferIn, int32_t start,
char *bufferOut,
char *needles, int32_t needleCount
) {
int32_t i = start, k = 0, j;
char c;
bool needleFound = false;
if(bufferIn[i] == '\0') return 0;
while((c = bufferIn[i++]) != '\0') {
for(j = 0; j < needleCount; j++) {
if(c != needles[j]) continue;
needleFound = true;
}
if(needleFound) break;
if(bufferOut != NULL) bufferOut[k] = c;
k++;
}
if(bufferOut != NULL) bufferOut[k] = '\0';
return k;
}
int32_t skipAhead(
char *bufferIn, int32_t start,
char *needles, int32_t needleCount
) {
char c;
int32_t j, k = 0, i = start;
bool needleFound;
while((c = bufferIn[i++]) != '\0') {
needleFound = false;
for(j = 0; j < needleCount; j++) {
if(c != needles[j]) continue;
needleFound = true;
break;
}
if(!needleFound) break;
k++;
}
return k;
}
void fileGetDirectory(char *file, char* buffer) {
char *c, *p;
int32_t i;
p = strrchr(file, FILE_PATH_SEP);
c = file;
i = 0;
do {
buffer[i++] = *c;
} while(++c < p);
buffer[i] = '\0';
}
bool fileListChildren(
char *directory,
char *buffer,
int32_t *count,
uint8_t *types,
char **children
) {
#if defined(_MSC_VER)
WIN32_FIND_DATA fdFile;
HANDLE hFind = NULL;
char sPath[2048];
int32_t i;
// Append wildcard
sprintf(sPath, "%s\\*.*", directory);
// Scan first
if((hFind = FindFirstFile(sPath, &fdFile)) == INVALID_HANDLE_VALUE) {
printf("Path not found: [%s]\n", directory);
return false;
}
// Iterate
i = 0;
do {
if(
strcmp(fdFile.cFileName, ".") == 0 ||
strcmp(fdFile.cFileName, "..") == 0
) continue;
// Get Full path.
sprintf(sPath, "%s\\%s", directory, fdFile.cFileName);
//Is the entity a File or Folder?
if(fdFile.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
types[i] = FILE_CHILD_TYPE_DIR;
} else {
types[i] = FILE_CHILD_TYPE_FILE;
}
children[i] = buffer + (i * FILE_CHILD_NAME_MAX);
strcpy(children[i], fdFile.cFileName);
i++;
} while(FindNextFile(hFind, &fdFile));
*count = i;
return true;
#else
struct dirent *de;
DIR *dr;
int32_t i;
// Open Dir
dr = opendir(directory);
if(dr == NULL) {
printf("Could not open directory");
return false;
}
// Iterate
i = 0;
while ((de = readdir(dr)) != NULL) {
// File or folder?
if(de->d_type != DT_REG) continue;
// Copy into child buffer
children[i] = buffer + (i * FILE_CHILD_NAME_MAX);
strcpy(children[i], de->d_name);
i++;
}
if(closedir(dr)) return false;
*count = i;
return true;
#endif
}

View File

@ -0,0 +1,75 @@
/**
* Copyright (c) 2021 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "common.h"
#define FILE_CHILD_TYPE_DIR 0x00
#define FILE_CHILD_TYPE_FILE 0x01
#define FILE_CHILD_NAME_MAX 512
#define FILE_CHILD_COUNT_MAX 64
#if defined(_MSC_VER)
#include <direct.h>
#include <windows.h>
#define getcwd _getcwd
#define FILE_PATH_SEP '\\'
#define fileMkdir(path, perms) _mkdir(path)
#elif defined(__GNUC__)
#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>
#define FILE_PATH_SEP '/'
#define fileMkdir(path, perms) mkdir(path, perms)
#endif
void fileNormalizeSlashes(char *string);
void fileMkdirp(char *path);
size_t assetReadString(FILE *file, char *buffer);
void fileGetDirectory(char *file, char* buffer);
bool fileListChildren(
char *directory,
char *buffer,
int32_t *count,
uint8_t *types,
char **children
);
/**
* Reads ahead to the first instance of the given character you provide.
*
* @param bufferIn Buffer to scan.
* @param start Start position within the buffer to scan from (inclusive).
* @param bufferOut Where to write the temporary data that was read ahead.
* @param needles Array of characters to scan for.
* @param needleCount How many elements are within the needles array.
* @return The count of characters skipped.
*/
int32_t readAhead(
char *bufferIn, int32_t start,
char *bufferOut,
char *needles, int32_t needleCount
);
/**
* Skips any characters found in the needles.
*
* @param bufferIn Buffer of chars to read.
* @param start Start of the buffer.
* @param needles Needles you are trying to skip.
* @param needleCount Count of needles in the needles array.
* @return The count of chars to skip ahead.
*/
int32_t skipAhead(
char *bufferIn, int32_t start,
char *needles, int32_t needleCount
);

View File

@ -0,0 +1,55 @@
/**
* Copyright (c) 2021 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "image.h"
#ifndef STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
#endif
#ifndef STB_IMAGE_RESIZE_IMPLEMENTATION
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include <stb_image_resize.h>
#endif
#ifndef STB_IMAGE_WRITE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image_write.h>
#endif
void imageCopy(
uint8_t *source, int32_t sourceWidth, int32_t sourceHeight,
uint8_t *dest, int32_t destWidth, int32_t destHeight,
int32_t cropX, int32_t cropY, int32_t cropWidth, int32_t cropHeight,
int32_t pasteX, int32_t pasteY,
int32_t channels
) {
int32_t x, y, c;
int32_t absX, absY;
int32_t sourceIndex, targetIndex;
if(cropX == -1) cropX = 0;
if(cropY == -1) cropY = 0;
if(cropWidth == -1) cropWidth = sourceWidth;
if(cropHeight == -1) cropHeight = sourceHeight;
if(pasteX == -1) pasteX = 0;
if(pasteY == -1) pasteY = 0;
for(x = cropX; x < cropX + cropWidth; x++) {
for(y = cropY; y < cropY + cropHeight; y++) {
absX = x - cropX + pasteX;
absY = y - cropY + pasteY;
if(absX >= destWidth || absY >= destHeight || absX < 0 || absY < 0)continue;
targetIndex = absY * destWidth + absX;
sourceIndex = y * sourceWidth + x;
for(c = 0; c < channels; c++) {
dest[(targetIndex*channels) + c] = source[(sourceIndex*channels) + c];
}
}
}
}

View File

@ -0,0 +1,22 @@
/**
* Copyright (c) 2021 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "common.h"
#include "file.h"
#include <stb_image.h>
#include <stb_image_resize.h>
#include <stb_image_write.h>
void imageCopy(
uint8_t *source, int32_t sourceWidth, int32_t sourceHeight,
uint8_t *dest, int32_t destWidth, int32_t destHeight,
int32_t cropX, int32_t cropY, int32_t cropWidth, int32_t cropHeight,
int32_t pasteX, int32_t pasteY,
int32_t channels
);

210
src/dawntools/utils/xml.c Normal file
View File

@ -0,0 +1,210 @@
/**
* 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';
}

67
src/dawntools/utils/xml.h Normal file
View File

@ -0,0 +1,67 @@
/**
* Copyright (c) 2021 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "common.h"
#include "file.h"
#define XML_DOING_NOTHING 0x00
#define XML_PARSING_TAG_NAME 0x01
#define XML_LOOKING_FOR_ATTRIBUTE 0x02
#define XML_PARSING_ATTRIBUTE_NAME 0x03
#define XML_LOOKING_FOR_ATTRIBUTE_VALUE 0x04
#define XML_PARSING_ATTRIBUTE_VALUE 0x05
#define XML_PARSING_VALUE 0x06
#define XML_PARSING_CHILD 0x07
#define XML_PARSING_CLOSE 0x08
#define XML_TEXT_BUFFER_MAX 256
#define XML_CHILD_COUNT_MAX 16
#define XML_ATTRIBUTE_MAX 16
typedef struct _xml_t xml_t;
typedef struct _xml_t {
char *node;
char *value;
char *attributeNames[XML_ATTRIBUTE_MAX];
char *attributeDatas[XML_ATTRIBUTE_MAX];
uint8_t attributeCount;
xml_t *children;
uint8_t childrenCount;
} xml_t;
/**
* Load an XML child from a string buffer.
*
* @param xml XML to load.
* @param data Data to parse
* @param i Character index within the data
* @return The index in the data string this XML node ends.
*/
int32_t xmlLoadChild(xml_t *xml, char *data, int32_t i);
/**
* Load an XML String into an XML memory.
*
* @param xml XML to load into.
* @param data XML string.
*/
void xmlLoad(xml_t *xml, char *data);
/**
* Dispose a previously loaded XML.
*
* @param xml XML to dispose.
*/
void xmlDispose(xml_t *xml);
int16_t xmlGetAttributeByName(xml_t *xml, char *name);
bool xmlIsWhitespace(char c);