New Language Tool

This commit is contained in:
2023-02-04 23:02:43 -08:00
parent 1416938479
commit fb34d7c16e
18 changed files with 747 additions and 497 deletions

View File

@ -1,86 +0,0 @@
undefined,UNDEFINED
hello.world,Hello World!
character.death.name,"Santa Muerte"
character.penny.name,Penny
scene.1.1,"...Huh? Where am I?"
scene.1.2,"Relax child, come forward, it is time."
scene.1.3,"Who said that? Where is that voice coming from?"
scene.1.4,"Hello, and welcome to the other side wanderer."
scene.1.5,"Other side? Wait that robe! You don't mean?"
scene.1.6,"Yeah let's speed things up here a bit. Normally I go on script but short on time right now unfortunately."
scene.1.7,"You died, dead, passed on, kaput. This is the space between spaces. You may be going up, you may be going down, that's not my choice, I'm just here to guide you."
scene.1.8,"...Are you?"
scene.1.9,"Yes, some call me the Grim Reaper, some Muerte, and a long time ago it was Torva messor."
scene.1.10,"Nowadays most people others just call me Death, but I prefer Nami, short for Izanami."
scene.1.11,"Truth be told I am not the reason you are here, I am just here to guide you, don't know why I keep getting lumped with this bad reputation of killing people."
scene.1.12,"Wait, so I am dead? For real? This has to be some kind of misunderstanding-wait I must be dreaming, that's it."
scene.1.121,"Oh no this can't be happening, this can't be real..."
scene.1.13,"If I had a penny for every time...
No, don't get it wrong, you are meant to be here."
scene.1.131,"I can't be.. you gotta send me back! How did I die?"
scene.1.132,"I don't know. Regardless, I can't send you back to your world. We are getting off topic anyway."
scene.1.133,"What?"
scene.1.134,"*ahem* I have a request of you child."
scene.1.14,"Me? Help you? Unless you're taking me back to my home I don't think you can convince me to help the likes of you."
scene.1.141,"Yes... See, normally I'd just take you to Charon. It is his role to take the newcomers where they need to go."
scene.1.142,"You know how it goes, I introduce you to Charon, you pay a coin, jump on a boat, and then you're not my problem any more."
scene.1.15,"You mean send me down the river styx? You're not helping your case."
scene.1.16,"Yes, well I would normally do that, but at the moment I have a bit of a problem with capacity."
scene.1.17,"Capacity? What kind of capacity issue?"
scene.1.18,"I manage a few worlds, not just the one that you come from."
scene.1.19,"In your world, people rely on technology and their own ability to progress civilization."
scene.1.20,"In another world, I manage people who raise and tame small pocket sized creatures with different abilities to progress their civilization."
scene.1.21,"But one world I manage has been having some real troubles lately."
scene.1.22,"This world contains humans alongside other intelligent creatures and species. They would be analagous to the medieval times in your world."
scene.1.23,"So far they have managed to keep a good balance by having some creatures weild weapons, and some use magical powers."
scene.1.24,"But lately there has been more cases of stronger creatures killing off the human population."
scene.1.25,"It's causing me to work overtime just to make sure I can get everyone where they need to go."
scene.1.26,"You have to work overtime?"
scene.1.27,"The benefits here are really good, my union gives us time and a half for overtime we do."
scene.1.28,"Death has unionized? That sounds horrible."
scene.1.29,"Yes I did, and please just call me Nami. My union is starting to threaten a strike and I need to try and prevent that."
scene.1.30,"I haven't been on strike since the 1940's when we had our last capacity issue."
scene.1.31,"The union negotiated a great dental plan after that. But we need to try and avoid striking this time."
scene.1.32,"Okay but I don't know how you think I can help."
scene.1.33,"It's easy. Now understand that I cannot send you back to your world, that is out of my control."
scene.1.34,"But there are no rules about you being sent to another world."
scene.1.35,"So, I send you to the medieval world, you get another crack at life and perhaps you can assist me in finding out how to stop all the deaths."
scene.1.36,"So, Nami, you want me to go to a world I don't know, that has monsters and a problem with lots of death, stuck in the medieval times, just to help out literal death?"
scene.1.37,"Not to mention I won't know anyone, I won't understand their language and don't know their culture."
scene.1.38,"I'll put it bluntly Nami, it sounds like I would be signing up for a short, miserable, lonely life, just for a chance to help you out."
scene.1.39,"That's great, thanks for offering to help out!"
scene.1.40,"I don't have a choice do I?"
scene.1.41,"No, besides we have already started. Now, stand still and take a deep breath I am going to send you over to the other world now."
scene.1.42,"I feel really tired all of a sudden... Wait does the Grim Reaper even have any kind of powers?"
scene.1.43,"Of course I do, now open your eyes..."
scene.2.1,"This scene has you waking in the new world"
scene.3.1,"This is the scene to prep you for the first game"
scene.4.1,"First Poker Game with friendlies"
scene.5.1,"After first game, wind up maybe shop instructions or something"
scene.6.1,"Talk with death probably, check in on you, etc."
scene.7.1,"Pre-Orc Battle"
scene.8.1,"Orc Battle"
scene.9.1,"Immediately Post Orc Battle"
scene.10.1,"Morning after orc battle and some foreshadowing"
scene.11.1,"Pre battle with theif"
scene.12.1,"Battle with theif"
unknown1,"There needs to be the big bad here."
scene.13.1,"After battle with theif, things cool off a bit."
scene.14.1,"Harem times maybe"
scene.15.1,"Harem times interrupted by some chaos"
scene.16.1,"I'm putting chaos here but this scene is probably redundant."
scene.17.1,"Chaos battle"
scene.18.1,"Post Chaos Battle, things are still gloomy"
unknown2,"Another big bad here probably."
scene.19.1,"Pre King Arrival, rumours"
scene.20.1,"King Arrival, Everyone's annoyed"
scene.21.1,"King in the bar, running his mouth"
scene.22.1,"King Fight"
scene.23.1,"Post King Fight, angry he leaves, town rallies around you"
scene.24.1," Go to one of the kingsmen who's working with the big bad, he's worried that the townspeople know more than they let on."
unknown3,"Something here has to bring the big bad to town."
unknown4,"It's revealed that death knows the big bad",
test.1,"Test"
test.2,"Test*"
Can't render this file because it has a wrong number of fields in line 84.

View File

@ -1,21 +0,0 @@
undefined,UNDEFINED
hello.world,Hello World!
character.penny.name,Penny
scene.1.1,"日本語パスコん"
scene.2.1,"This scene has you waking in the new world"
scene.3.1,"This is the scene to prep you for the first game"
scene.4.1,"First Poker Game with friendlies"
scene.5.1,"After first game, wind up maybe shop instructions or something"
scene.6.1,"Talk with death probably, check in on you, etc."
scene.7.1,"Pre-Orc Battle"
scene.8.1,"Orc Battle"
scene.9.1,"Immediately Post Orc Battle"
scene.10.1,"Morning after orc battle and some foreshadowing"
scene.11.1,"Pre battle with theif"
scene.12.1,"Battle with theif"
scene.13.1,"After battle with theif, things cool off a bit."
scene.14.1,"Harem times maybe"
scene.15.1,"Harem times interrupted by some chaos"
scene.16.1,"I'm putting chaos here but this scene is probably redundant."
scene.17.1,"Chaos battle"
scene.18.1,"Post Chaos Battle, things are still gloomy"
1 undefined UNDEFINED
2 hello.world Hello World!
3 character.penny.name Penny
4 scene.1.1 日本語パスコん
5 scene.2.1 This scene has you waking in the new world
6 scene.3.1 This is the scene to prep you for the first game
7 scene.4.1 First Poker Game with friendlies
8 scene.5.1 After first game, wind up maybe shop instructions or something
9 scene.6.1 Talk with death probably, check in on you, etc.
10 scene.7.1 Pre-Orc Battle
11 scene.8.1 Orc Battle
12 scene.9.1 Immediately Post Orc Battle
13 scene.10.1 Morning after orc battle and some foreshadowing
14 scene.11.1 Pre battle with theif
15 scene.12.1 Battle with theif
16 scene.13.1 After battle with theif, things cool off a bit.
17 scene.14.1 Harem times maybe
18 scene.15.1 Harem times interrupted by some chaos
19 scene.16.1 I'm putting chaos here but this scene is probably redundant.
20 scene.17.1 Chaos battle
21 scene.18.1 Post Chaos Battle, things are still gloomy

View File

@ -0,0 +1,91 @@
<locale>
<language name="en" />
<group key="scene">
<group key="1">
<group key="1">
<string lang="en">...Huh? Where am I?</string>
</group>
</group>
<group key="2">
<string lang="en">This scene has you waking in the new world</string>
</group>
<group key="3">
<string lang="en">This is the scene to prep you for the first game</string>
</group>
<group key="4">
<string lang="en">First Poker Game with friendlies</string>
</group>
<group key="5">
<string lang="en">After first game, wind up maybe shop instructions or something</string>
</group>
<group key="6">
<string lang="en">Talk with death probably, check in on you, etc.</string>
</group>
<group key="7">
<string lang="en">Pre-Orc Battle</string>
</group>
<group key="8">
<string lang="en">Orc Battle</string>
</group>
<group key="9">
<string lang="en">Immediately Post Orc Battle</string>
</group>
<group key="10">
<string lang="en">Morning after orc battle and some foreshadowing</string>
</group>
<group key="11">
<string lang="en">Pre battle with theif</string>
</group>
<group key="12">
<string lang="en">Battle with theif</string>
</group>
<group key="13">
<string lang="en">There needs to be the big bad here.</string>
</group>
<group key="14">
<string lang="en">After battle with theif, things cool off a bit.</string>
</group>
<group key="15">
<string lang="en">Harem times maybe</string>
</group>
<group key="16">
<string lang="en">Harem times interrupted by some chaos</string>
</group>
<group key="17">
<string lang="en">I'm putting chaos here but this scene is probably redundant.</string>
</group>
<group key="18">
<string lang="en">Chaos battle</string>
</group>
<group key="19">
<string lang="en">Post Chaos Battle, things are still gloomy</string>
</group>
<group key="20">
<string lang="en">Another big bad here probably.</string>
</group>
<group key="21">
<string lang="en">Pre King Arrival, rumours</string>
</group>
<group key="22">
<string lang="en">King Arrival, Everyone's annoyed</string>
</group>
<group key="23">
<string lang="en">King in the bar, running his mouth</string>
</group>
<group key="24">
<string lang="en">King Fight</string>
</group>
<group key="25">
<string lang="en">Post King Fight, angry he leaves, town rallies around you</string>
</group>
<group key="26">
<string lang="en">Go to one of the kingsmen who's working with the big bad, he's worried that the townspeople know more than they let on.</string>
</group>
<group key="27">
<string lang="en">Something here has to bring the big bad to town.</string>
</group>
<group key="28">
<string lang="en">It's revealed that death knows the big bad</string>
</group>
</group>
</locale>

View File

@ -1,13 +0,0 @@
<root
name="UITest"
UISprite="ui/UISprite.hpp"
UIEmpty="ui/UIEmpty.hpp"
UIGrid="ui/UIGrid.hpp"
>
<UIGrid name="grid" align="stretch stretch 0 0 0 0" rows="2" columns="2">
<UISprite name="rect0" grid="stretch stretch 0 0" color="COLOR_RED" />
<UISprite name="rect1" grid="stretch stretch 1 0" color="COLOR_BLUE" />
<UISprite name="rect2" grid="stretch stretch 0 1" color="COLOR_WHITE" />
<UISprite name="rect3" grid="stretch stretch 1 1" color="COLOR_BLACK" />
</UIGrid>
</root>

View File

@ -0,0 +1,36 @@
# This is a sample VN Scene that I am creating to create a scene generator.
# The scene generator will simply create a series of events, nothing more.
# Infact, the scene generator really is just a scene event with trailed events
# Generator.
# Scene name is probably going to be inherited from either the target or the
# filename
# By default the mode we are in is in TEXT mode, unless you explicitly change
# the mode. The text mode takes the person, emotion and text string and tries
# to convert it to this C++ equivallent code;
# new VisualNovelTextboxEvent(vnManager, this->[person]->vnCharacter, this->[person]->[emotion], [text])
# Here is our first example. We take death, give her an emotion, and write some
# text.
death:happy: scene.1.1
# Here, we inherit the existing emotion. I've also omitted the space because it
# is just there for show.
death:scene.1.1
# Now let's fade our character in. Fade char in events start like this
fade death in quad over 1.0
# Could also be written like any of these
# fade death out
# fade death in
# fade death out over 1.0 seconds
# fade death in over 1.0 seconds
# fade death in linear
# fade death out linear over 10 seconds
# fade death out over 10
# Basically the format just needs to be
# fade [character] [in/out] (easing function) (time)
# Each time a fade occurs the parameters will be remembered, and used for any
# other fade after that, so you just need to define the easing curve once per
# scene.

94
lint.js Normal file
View File

@ -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);
})();

View File

@ -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
)

View File

@ -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)
# 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()

View File

@ -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

View File

@ -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()
# 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()

View File

@ -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}
)

View File

@ -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;
}

View File

@ -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 <memory.h>
}
#include <iostream>
#include <map>
#include <vector>
#include <algorithm>
#include <iterator>
struct LanguageString {
std::string key;
std::string value;
};
int32_t parseString(
xml_t *stringNode,
std::string key,
std::map<std::string,std::vector<struct LanguageString>> *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<struct LanguageString>()));
}
(*strings)[lang].push_back(str);
return 0;
}
int32_t parseGroup(
xml_t *groupNode,
std::string key,
std::map<std::string,std::vector<struct LanguageString>> *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 <language> tags
std::vector<std::string> 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<std::string, std::vector<struct LanguageString>> 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<std::string> 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<std::string> 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;
}

View File

@ -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';
}

View File

@ -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

View File

@ -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)

View File

@ -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}
)

View File

@ -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 <memory.h>
}
#include <iostream>
#include <vector>
int main(int32_t argc, char *args[]) {
}