From 223f8566b99f9ccba3945f78dba5431686bb5940 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Sat, 4 Feb 2023 23:02:43 -0800 Subject: [PATCH] New Language Tool --- lint.js | 94 ++++ src/dawnpokergame/CMakeLists.txt | 8 +- src/dawntools/CMakeLists.txt | 22 +- src/dawntools/display/uigen/CMakeLists.txt | 2 +- src/dawntools/locale/CMakeLists.txt | 30 +- .../locale/languagegen/CMakeLists.txt | 47 +- src/dawntools/locale/languagegen/main.c | 112 ----- src/dawntools/locale/languagegen/main.cpp | 218 +++++++++ src/dawntools/utils/xml.c | 419 +++++++++--------- src/dawntools/utils/xml.h | 2 +- src/dawntools/visualnovel/CMakeLists.txt | 6 + .../visualnovel/vnscenegen/CMakeLists.txt | 22 + src/dawntools/visualnovel/vnscenegen/main.cpp | 15 + 13 files changed, 620 insertions(+), 377 deletions(-) create mode 100644 lint.js delete mode 100644 src/dawntools/locale/languagegen/main.c create mode 100644 src/dawntools/locale/languagegen/main.cpp create mode 100644 src/dawntools/visualnovel/CMakeLists.txt create mode 100644 src/dawntools/visualnovel/vnscenegen/CMakeLists.txt create mode 100644 src/dawntools/visualnovel/vnscenegen/main.cpp diff --git a/lint.js b/lint.js new file mode 100644 index 00000000..b00b5479 --- /dev/null +++ b/lint.js @@ -0,0 +1,94 @@ +const fs = require('fs'); +const path = require('path'); + +const DIR_SOURCES = path.resolve('src'); + +const fileGetContents = filePath => fs.readFileSync(filePath, 'utf-8'); + +const ensureCopyright = (file, contents) => { + if( + contents.includes('Copyright (c)') && + contents.includes('MIT') + ) return; + throw new Error(`${file} is missing its copyright!`); +} + +const ensurePragma = (file, contents) => { + if(contents.includes('#pragma once')) return; + throw new Error(`${file} is missing a #pragma once`); +} + + +const fileHandleCpp = filePath => { + // Load contents + const contents = fileGetContents(filePath); + ensureCopyright(filePath, contents); +} + +const fileHandleC = filePath => { + // Load contents + const contents = fileGetContents(filePath); + ensureCopyright(filePath, contents); +} + +const fileHandleHpp = filePath => { + // Load contents + const contents = fileGetContents(filePath); + + ensureCopyright(filePath, contents); + ensurePragma(filePath, contents); +} + +const fileHandleH = filePath => { + // Load contents + const contents = fileGetContents(filePath); + + ensureCopyright(filePath, contents); + ensurePragma(filePath, contents); +} + +const fileHandleCmake = filePath => { + // Load contents + const contents = fileGetContents(filePath); + + // Check for the copyright + ensureCopyright(filePath, contents); +} + + + +const fileScan = filePath => { + const ext = path.extname(filePath).replace(/\./g, ''); + const base = path.basename(filePath); + + if(ext === 'cpp') { + fileHandleCpp(filePath); + } else if(ext === 'hpp') { + fileHandleHpp(filePath); + } else if(ext === 'c') { + fileHandleC(filePath); + } else if(ext === 'h') { + fileHandleH(filePath); + }else if(base === 'CMakeLists.txt') { + fileHandleCmake(filePath); + } else { + throw new Error(`Unknown file type ${filePath}`); + } +}; + +const dirScan = directory => { + const contents = fs.readdirSync(directory); + contents.forEach(filePath => { + const abs = path.join(directory, filePath); + const stat = fs.statSync(abs); + if(stat.isDirectory()) { + dirScan(abs); + } else { + fileScan(abs); + } + }); +} + +(() => { + dirScan(DIR_SOURCES); +})(); \ No newline at end of file diff --git a/src/dawnpokergame/CMakeLists.txt b/src/dawnpokergame/CMakeLists.txt index dedd4ee0..2fd520ef 100644 --- a/src/dawnpokergame/CMakeLists.txt +++ b/src/dawnpokergame/CMakeLists.txt @@ -30,7 +30,7 @@ add_subdirectory(scenes) set(DIR_GAME_ASSETS games/pokergame) tool_texture(texture_test texture_test.png) -tool_language(language_en ${DIR_GAME_ASSETS}/locale/en.csv) +tool_language(locale_poker ${DIR_GAME_ASSETS}/locale/locale.xml) tool_tileset(tileset_death texture_death ${DIR_GAME_ASSETS}/characters/death/sheet.png 1 3) tool_tileset(tileset_penny texture_penny ${DIR_GAME_ASSETS}/characters/penny/sheet.png 1 3) @@ -39,10 +39,8 @@ tool_truetype(truetype_alice ${DIR_GAME_ASSETS}/font/Alice-Regular.ttf truetype_ tool_audio(audio_test borrowed/sample_short.wav) -tool_ui(ui_test ${DIR_GAME_ASSETS}/ui/uitest.xml) - add_dependencies(${DAWN_TARGET_NAME} - language_en + locale_poker tileset_death tileset_penny @@ -52,6 +50,4 @@ add_dependencies(${DAWN_TARGET_NAME} texture_test audio_test - - ui_test ) \ No newline at end of file diff --git a/src/dawntools/CMakeLists.txt b/src/dawntools/CMakeLists.txt index d73b2021..30206d91 100644 --- a/src/dawntools/CMakeLists.txt +++ b/src/dawntools/CMakeLists.txt @@ -1,9 +1,13 @@ -# Copyright (c) 2021 Dominic Msters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -add_subdirectory(audio) -add_subdirectory(display) -add_subdirectory(file) -add_subdirectory(locale) \ No newline at end of file +# Copyright (c) 2021 Dominic Msters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +add_subdirectory(audio) +add_subdirectory(display) +add_subdirectory(file) +add_subdirectory(locale) + +if(DAWN_VISUAL_NOVEL) + add_subdirectory(visualnovel) +endif() \ No newline at end of file diff --git a/src/dawntools/display/uigen/CMakeLists.txt b/src/dawntools/display/uigen/CMakeLists.txt index 9fb7a2c9..70ed4992 100644 --- a/src/dawntools/display/uigen/CMakeLists.txt +++ b/src/dawntools/display/uigen/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2021 Dominic Msters +# Copyright (c) 2023 Dominic Msters # # This software is released under the MIT License. # https://opensource.org/licenses/MIT diff --git a/src/dawntools/locale/CMakeLists.txt b/src/dawntools/locale/CMakeLists.txt index 65ad32d6..79506401 100644 --- a/src/dawntools/locale/CMakeLists.txt +++ b/src/dawntools/locale/CMakeLists.txt @@ -1,15 +1,15 @@ -# Copyright (c) 2021 Dominic Msters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -add_subdirectory(languagegen) - -# Language Tool -function(tool_language target in) - add_custom_target(${target} - COMMAND languagegen "${DAWN_ASSETS_SOURCE_DIR}/${in}" "${DAWN_ASSETS_BUILD_DIR}/${target}" - COMMENT "Generating texture ${target} from ${in}" - DEPENDS languagegen - ) -endfunction() \ No newline at end of file +# Copyright (c) 2021 Dominic Msters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +add_subdirectory(languagegen) + +# Language Tool +function(tool_language target in) + add_custom_target(${target} + COMMAND languagegen "${DAWN_ASSETS_SOURCE_DIR}/${in}" "${DAWN_ASSETS_BUILD_DIR}" + COMMENT "Generating language set ${target} from ${in}" + DEPENDS languagegen + ) +endfunction() diff --git a/src/dawntools/locale/languagegen/CMakeLists.txt b/src/dawntools/locale/languagegen/CMakeLists.txt index e628bc38..423852ae 100644 --- a/src/dawntools/locale/languagegen/CMakeLists.txt +++ b/src/dawntools/locale/languagegen/CMakeLists.txt @@ -1,25 +1,24 @@ -# Copyright (c) 2021 Dominic Msters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -# Texture Build Tool -project(languagegen VERSION 1.0) -add_executable(languagegen) -target_sources(languagegen - PRIVATE - main.c - ../../utils/file.c - ../../utils/image.c - ../../utils/csv.c -) -target_include_directories(languagegen - PUBLIC - ${CMAKE_CURRENT_LIST_DIR}/../../ - ${CMAKE_CURRENT_LIST_DIR} -) -target_link_libraries(languagegen - PUBLIC - ${DAWN_BUILD_HOST_LIBS} - stb +# Copyright (c) 2021 Dominic Msters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Texture Build Tool +project(languagegen VERSION 2.0) +add_executable(languagegen) +target_sources(languagegen + PRIVATE + main.cpp + ../../utils/file.c + ../../utils/csv.c + ../../utils/xml.c +) +target_include_directories(languagegen + PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/../../ + ${CMAKE_CURRENT_LIST_DIR} +) +target_link_libraries(languagegen + PUBLIC + ${DAWN_BUILD_HOST_LIBS} ) \ No newline at end of file diff --git a/src/dawntools/locale/languagegen/main.c b/src/dawntools/locale/languagegen/main.c deleted file mode 100644 index a37aedd9..00000000 --- a/src/dawntools/locale/languagegen/main.c +++ /dev/null @@ -1,112 +0,0 @@ -/** - * 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/csv.h" - -int main(int argc, char *argv[]) { - FILE *file; - char path[FILENAME_MAX + 1]; - csv_t csv; - char *in; - char *out; - char *buffer; - char sep = '|'; - - 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.language", out); - // file = fopen(path, "rb"); - // if(file != NULL) { - // fclose(file); - // return 0; - // } - - // Read in original CSV string - file = fopen(in, "rb"); - if(file == NULL) { - printf("Failed to open 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 - buffer = malloc(sizeof(char) * (fileSize + 1)); - size_t readSize = fread(buffer, 1, fileSize, file); - fclose(file); - if(readSize < fileSize) { - printf("Failed to read all data from CSV\n"); - free(buffer); - return 1; - } - buffer[fileSize] = '\0'; - - csvParse(buffer, &csv); - free(buffer); - - // Prepare output file for writing. - sprintf(path, "%s.language", out); - fileMkdirp(path); - file = fopen(path, "wb"); - if(file == NULL) { - printf("Failed to create output language file\n"); - csvDispose(&csv); - return 1; - } - - // Iterate over the CSV - for(int32_t y = 0; y < csv.rowCount; y++) { - // Ensure valid line - if(csv.cellCounts[y] != 2) { - printf("Failed to parse language. Line %i has %i cells instead of 2\n", y, csv.cellCounts); - fclose(file); - csvDispose(&csv); - return 1; - } - - char *key = csvGetCell(&csv, y, 0); - char *value = csvGetCell(&csv, y, 1); - - // 23/01/14 - Replace \r in CSV. - stringRemoveAll(key, '\r'); - stringRemoveAll(value, '\r'); - if(strlen(key) <= 0 || strlen(value) <= 0) { - printf("Failed to parse language. Line %i has an invalid string\n", y); - fclose(file); - csvDispose(&csv); - return 1; - } - - fwrite(key, sizeof(char), strlen(key), file); - fwrite(&sep, sizeof(char), 1, file); - fwrite(value, sizeof(char), strlen(value), file); - fwrite(&sep, sizeof(char), 1, file); - } - - // Finished writing - fclose(file); - csvDispose(&csv); - - return 0; -} \ No newline at end of file diff --git a/src/dawntools/locale/languagegen/main.cpp b/src/dawntools/locale/languagegen/main.cpp new file mode 100644 index 00000000..a2b57854 --- /dev/null +++ b/src/dawntools/locale/languagegen/main.cpp @@ -0,0 +1,218 @@ +/** + * Copyright (c) 2021 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +extern "C" { + #include "../../utils/common.h" + #include "../../utils/file.h" + #include "../../utils/csv.h" + #include "../../utils/xml.h" + #include +} + +#include +#include +#include +#include +#include + +struct LanguageString { + std::string key; + std::string value; +}; + +int32_t parseString( + xml_t *stringNode, + std::string key, + std::map> *strings +) { + auto attrLang = xmlGetAttributeByName(stringNode, "lang"); + if(attrLang == -1) { + std::cout << "String is missing lang parameter." << std::endl; + return -1; + } + + std::string lang(stringNode->attributeDatas[attrLang]); + struct LanguageString str; + str.key = key; + str.value = std::string(stringNode->value); + + auto existing = (*strings).find(lang); + if(existing == (*strings).end()) { + (*strings).insert(std::make_pair(lang, std::vector())); + } + (*strings)[lang].push_back(str); + return 0; +} + +int32_t parseGroup( + xml_t *groupNode, + std::string key, + std::map> *strings +) { + int32_t ret; + + auto attrKey = xmlGetAttributeByName(groupNode, "key"); + if(attrKey == -1) { + std::cout << "Group node is missing key" << std::endl; + return 1; + } + + if(key.size() > 0) key += "."; + key += std::string(groupNode->attributeDatas[attrKey]); + + for(int32_t i = 0; i < groupNode->childrenCount; i++) { + auto c = groupNode->children + i; + if(std::string(c->node) == "string") { + ret = parseString(c, key, strings); + if(ret != 0) return ret; + } else if(std::string(c->node) == "group") { + ret = parseGroup(c, key, strings); + if(ret != 0) return ret; + } + } + + return 0; +} + +int main(int argc, char *argv[]) { + if(argc != 3) { + std::cout << "Invalid number of arguments provided to language gen!" << std::endl; + return 1; + } + + char *fileInName = argv[1]; + fileNormalizeSlashes(fileInName); + + FILE *fileIn = fopen(fileInName, "rb"); + if(fileIn == NULL) { + std::cout << "Failed to open input file " << fileInName << std::endl; + return 1; + } + + auto size = assetReadString(fileIn, NULL); + char *buffer = (char *)malloc(sizeof(char) * size); + if(buffer == NULL) { + std::cout << "Failed to allocate memory for locale string XML" << std::endl; + fclose(fileIn); + return 1; + } + + assetReadString(fileIn, buffer); + fclose(fileIn); + + xml_t xml; + xmlLoad(&xml, buffer); + free(buffer); + + // Begin parsing. Start by looking for the tags + std::vector languages; + for(int32_t i = 0; i < xml.childrenCount; i++) { + auto c = xml.children + i; + if(std::string(c->node) != "language") continue; + auto attrName = xmlGetAttributeByName(c, "name"); + + if(attrName == -1) { + std::cout << "Missing name param on language node" << std::endl; + xmlDispose(&xml); + return 1; + } + languages.push_back(std::string(c->attributeDatas[attrName])); + } + + // Now begin actually parsing + std::map> strings; + for(int32_t i = 0; i < xml.childrenCount; i++) { + auto c = xml.children + i; + if(std::string(c->node) == "group") { + auto ret = parseGroup(c, "", &strings); + if(ret != 0) { + xmlDispose(&xml); + return ret; + } + } else if(std::string(c->node) == "string") { + std::cout << "String cannot be a root node" << std::endl; + xmlDispose(&xml); + return 1; + } + } + + xmlDispose(&xml); + + // Now we validate each lang has each key. + std::vector keys; + + auto it = strings.begin(); + while(it != strings.end()) { + auto it2 = it->second.begin(); + while(it2 != it->second.end()) { + auto key = it2->key; + auto exist = std::find(keys.begin(), keys.end(), key); + if(exist == keys.end()) { + keys.push_back(key); + } + it2++; + } + ++it; + } + + // Now we actually parse each string, validating as we go. + it = strings.begin(); + while(it != strings.end()) { + std::vector itKeys; + + std::string bufferOut = ""; + + auto it2 = it->second.begin(); + while(it2 != it->second.end()) { + auto l = *it2; + itKeys.push_back(l.key); + bufferOut += l.key + "|" + l.value + "|"; + it2++; + } + + std::string filenameOut(argv[2]); + filenameOut += "/nlanguage_" + it->first + ".language"; + + char *filenameOutC = (char *)malloc(sizeof(char) * (filenameOut.size() + 1)); + if(filenameOutC == NULL) { + std::cout << "Failed to allocate filename memory" << std::endl; + return 1; + } + strcpy(filenameOutC, filenameOut.c_str()); + fileNormalizeSlashes(filenameOutC); + + fileMkdirp(filenameOutC); + FILE *fileOut = fopen(filenameOutC, "wb"); + free(filenameOutC); + if(fileOut == NULL) { + std::cout << "Failed to create output file " << filenameOut << std::endl; + return 1; + } + + const char *strOut = bufferOut.c_str(); + fwrite(strOut, sizeof(char), strlen(strOut), fileOut); + fclose(fileOut); + + auto it3 = keys.begin(); + while(it3 != keys.end()) { + auto key = *it3; + auto inIt = std::find(itKeys.begin(), itKeys.end(), key); + if(inIt == itKeys.end()) { + std::cout << "Locale " << it->first << " missing key " << key << std::endl; + } + it3++; + } + + if(itKeys.size() != keys.size()) { + std::cout << "Locale is missing some keys, see above" << std::endl; + return 1; + } + ++it; + } + + return 0; +} \ No newline at end of file diff --git a/src/dawntools/utils/xml.c b/src/dawntools/utils/xml.c index 8c6c43ed..caeea259 100644 --- a/src/dawntools/utils/xml.c +++ b/src/dawntools/utils/xml.c @@ -1,210 +1,211 @@ -/** - * 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'; +/** + * 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'; } \ No newline at end of file diff --git a/src/dawntools/utils/xml.h b/src/dawntools/utils/xml.h index 1bfacd04..6e045b36 100644 --- a/src/dawntools/utils/xml.h +++ b/src/dawntools/utils/xml.h @@ -19,7 +19,7 @@ #define XML_PARSING_CHILD 0x07 #define XML_PARSING_CLOSE 0x08 -#define XML_TEXT_BUFFER_MAX 256 +#define XML_TEXT_BUFFER_MAX 2048 #define XML_CHILD_COUNT_MAX 128 #define XML_ATTRIBUTE_MAX 128 diff --git a/src/dawntools/visualnovel/CMakeLists.txt b/src/dawntools/visualnovel/CMakeLists.txt new file mode 100644 index 00000000..9466b558 --- /dev/null +++ b/src/dawntools/visualnovel/CMakeLists.txt @@ -0,0 +1,6 @@ +# Copyright (c) 2023 Dominic Msters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +add_subdirectory(vnscenegen) \ No newline at end of file diff --git a/src/dawntools/visualnovel/vnscenegen/CMakeLists.txt b/src/dawntools/visualnovel/vnscenegen/CMakeLists.txt new file mode 100644 index 00000000..3daf5c33 --- /dev/null +++ b/src/dawntools/visualnovel/vnscenegen/CMakeLists.txt @@ -0,0 +1,22 @@ +# Copyright (c) 2023 Dominic Msters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Texture Build Tool +project(vnscenegen VERSION 1.0) +add_executable(vnscenegen) +target_sources(vnscenegen + PRIVATE + main.cpp + ../../utils/file.c +) +target_include_directories(vnscenegen + PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/../../ + ${CMAKE_CURRENT_LIST_DIR} +) +target_link_libraries(vnscenegen + PUBLIC + ${DAWN_BUILD_HOST_LIBS} +) \ No newline at end of file diff --git a/src/dawntools/visualnovel/vnscenegen/main.cpp b/src/dawntools/visualnovel/vnscenegen/main.cpp new file mode 100644 index 00000000..5b4376ae --- /dev/null +++ b/src/dawntools/visualnovel/vnscenegen/main.cpp @@ -0,0 +1,15 @@ +// Copyright (c) 2023 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +extern "C" { + #include "../../utils/file.h" + #include +} +#include +#include + +int main(int32_t argc, char *args[]) { + +} \ No newline at end of file