mirror of
https://github.com/lifegpc/msg-tool.git
synced 2026-06-08 05:48:46 +08:00
705 lines
29 KiB
Rust
705 lines
29 KiB
Rust
//! Artemis Engine AST file (.ast)
|
|
mod dump;
|
|
mod parser;
|
|
mod text;
|
|
mod types;
|
|
|
|
use crate::scripts::base::*;
|
|
use crate::types::*;
|
|
use crate::utils::encoding::*;
|
|
use anyhow::Result;
|
|
use std::io::Write;
|
|
|
|
pub use dump::Dumper;
|
|
pub use parser::Parser;
|
|
pub use types::*;
|
|
|
|
#[derive(Debug)]
|
|
/// The builder for Artemis AST scripts.
|
|
pub struct AstScriptBuilder {}
|
|
|
|
impl AstScriptBuilder {
|
|
/// Creates a new instance of `AstScriptBuilder`.
|
|
pub fn new() -> Self {
|
|
AstScriptBuilder {}
|
|
}
|
|
}
|
|
|
|
impl ScriptBuilder for AstScriptBuilder {
|
|
fn default_encoding(&self) -> Encoding {
|
|
Encoding::Utf8
|
|
}
|
|
|
|
fn build_script(
|
|
&self,
|
|
buf: Vec<u8>,
|
|
_filename: &str,
|
|
encoding: Encoding,
|
|
_archive_encoding: Encoding,
|
|
config: &ExtraConfig,
|
|
_archive: Option<&Box<dyn Script>>,
|
|
) -> Result<Box<dyn Script>> {
|
|
Ok(Box::new(AstScript::new(buf, encoding, config)?))
|
|
}
|
|
|
|
fn extensions(&self) -> &'static [&'static str] {
|
|
&["ast"]
|
|
}
|
|
|
|
fn script_type(&self) -> &'static ScriptType {
|
|
&ScriptType::Artemis
|
|
}
|
|
|
|
fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
|
|
let parser = parser::Parser::new(&buf[..buf_len], Encoding::Utf8);
|
|
if parser.try_parse_header().is_ok() {
|
|
Some(15)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
/// The Artemis AST script.
|
|
pub struct AstScript {
|
|
ast: AstFile,
|
|
indent: Option<usize>,
|
|
max_line_width: usize,
|
|
no_indent: bool,
|
|
lang: Option<String>,
|
|
}
|
|
|
|
impl AstScript {
|
|
/// Creates a new Artemis AST script from the given buffer.
|
|
///
|
|
/// * `buf` - The buffer containing the AST data.
|
|
/// * `encoding` - The encoding used for the AST data.
|
|
/// * `config` - Extra configuration options.
|
|
pub fn new(buf: Vec<u8>, encoding: Encoding, config: &ExtraConfig) -> Result<Self> {
|
|
let parser = parser::Parser::new(&buf, encoding);
|
|
let ast = parser.parse()?;
|
|
Ok(AstScript {
|
|
ast,
|
|
indent: config.artemis_indent,
|
|
max_line_width: config.artemis_max_line_width,
|
|
no_indent: config.artemis_no_indent,
|
|
lang: config.artemis_ast_lang.clone(),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Script for AstScript {
|
|
fn default_output_script_type(&self) -> OutputScriptType {
|
|
OutputScriptType::Json
|
|
}
|
|
|
|
fn default_format_type(&self) -> FormatOptions {
|
|
FormatOptions::None
|
|
}
|
|
|
|
fn extract_messages(&self) -> Result<Vec<Message>> {
|
|
let mut messages = Vec::new();
|
|
let ast = &self.ast.ast;
|
|
let mut lang: Option<&str> = self.lang.as_ref().map(|s| s.as_str());
|
|
// old version
|
|
if ast["label"]["top"]["block"].is_null() && ast["text"].is_array() {
|
|
let text = &ast["text"];
|
|
let mut text_index = 1i64;
|
|
for block in ast.members() {
|
|
if block.is_array() {
|
|
let savetitle = &block[Key("savetitle")];
|
|
if savetitle.is_array() {
|
|
if let Some(lang) = lang {
|
|
if let Some(title) = savetitle[lang].as_str() {
|
|
messages.push(Message {
|
|
name: None,
|
|
message: title.to_string(),
|
|
});
|
|
} else if let Some(title) = savetitle["text"].as_str() {
|
|
messages.push(Message {
|
|
name: None,
|
|
message: title.to_string(),
|
|
});
|
|
}
|
|
} else if let Some(title) = savetitle["text"].as_str() {
|
|
messages.push(Message {
|
|
name: None,
|
|
message: title.to_string(),
|
|
});
|
|
}
|
|
}
|
|
if !block[Key("text")].is_null() {
|
|
let tex = &text[NumKey(text_index)];
|
|
text_index += 1;
|
|
if tex.is_array() {
|
|
let lan = match lang {
|
|
Some(l) => l,
|
|
None => {
|
|
for l in tex.kv_keys() {
|
|
if l.is_str() && l != "vo" && l != "name" {
|
|
lang = l.as_str();
|
|
break;
|
|
}
|
|
}
|
|
match lang {
|
|
Some(l) => l,
|
|
// No text found, continue to next block
|
|
None => continue,
|
|
}
|
|
}
|
|
};
|
|
let mut te = &tex[lan];
|
|
if te.is_null() {
|
|
for l in tex.kv_keys() {
|
|
if l != "vo" && l != "name" {
|
|
te = &tex[l];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
let name = &tex["name"];
|
|
let nam = if name.is_array() {
|
|
if let Some(lang) = lang {
|
|
if let Some(n) = name[lang].as_string() {
|
|
Some(n)
|
|
} else if let Some(n) = name["name"].as_string() {
|
|
Some(n)
|
|
} else {
|
|
None
|
|
}
|
|
} else if let Some(n) = name["name"].as_string() {
|
|
Some(n)
|
|
} else {
|
|
None
|
|
}
|
|
} else {
|
|
None
|
|
};
|
|
for item in te.members() {
|
|
let message = text::TextGenerator::new().generate(item)?;
|
|
messages.push(Message {
|
|
name: nam.clone(),
|
|
message: message.replace("<rt2>", "\n").replace("<ret2>", "\n"),
|
|
});
|
|
}
|
|
}
|
|
} else if !block[Key("select")].is_null() {
|
|
let tex = &text[NumKey(text_index)]["select"];
|
|
text_index += 1;
|
|
if tex.is_array() {
|
|
let lan = match lang {
|
|
Some(l) => l,
|
|
None => {
|
|
for l in tex.kv_keys() {
|
|
if l.is_str() && l != "vo" && l != "name" {
|
|
lang = l.as_str();
|
|
break;
|
|
}
|
|
}
|
|
match lang {
|
|
Some(l) => l,
|
|
// No text found, continue to next block
|
|
None => continue,
|
|
}
|
|
}
|
|
};
|
|
let mut te = &tex[lan];
|
|
if te.is_null() {
|
|
for l in tex.kv_keys() {
|
|
if l != "vo" && l != "name" {
|
|
te = &tex[l];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
for item in te.members() {
|
|
if let Some(select) = item.as_str() {
|
|
messages.push(Message {
|
|
name: None,
|
|
message: select.to_string(),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return Ok(messages);
|
|
}
|
|
let mut block_name = ast["label"]["top"]["block"]
|
|
.as_str()
|
|
.ok_or(anyhow::anyhow!("Missing top block name"))?;
|
|
let mut block = &ast[block_name];
|
|
loop {
|
|
let savetitle = &block[Key("savetitle")];
|
|
if savetitle.is_array() {
|
|
if let Some(lang) = lang {
|
|
if let Some(title) = savetitle[lang].as_str() {
|
|
messages.push(Message {
|
|
name: None,
|
|
message: title.to_string(),
|
|
});
|
|
} else if let Some(title) = savetitle["text"].as_str() {
|
|
messages.push(Message {
|
|
name: None,
|
|
message: title.to_string(),
|
|
});
|
|
}
|
|
} else if let Some(title) = savetitle["text"].as_str() {
|
|
messages.push(Message {
|
|
name: None,
|
|
message: title.to_string(),
|
|
});
|
|
}
|
|
}
|
|
let text = &block["text"];
|
|
if text.is_array() {
|
|
let lan = match lang {
|
|
Some(l) => l,
|
|
None => {
|
|
for l in text.kv_keys() {
|
|
if l.is_str() && l != "vo" {
|
|
lang = l.as_str();
|
|
break;
|
|
}
|
|
}
|
|
match lang {
|
|
Some(l) => l,
|
|
// No text found, continue to next block
|
|
None => {
|
|
block_name = match block["linknext"].as_str() {
|
|
Some(name) => name,
|
|
None => break,
|
|
};
|
|
block = &ast[block_name];
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
let mut tex = &text[lan];
|
|
if tex.is_null() {
|
|
for l in text.kv_keys() {
|
|
if l != "vo" {
|
|
tex = &text[l];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
for item in tex.members() {
|
|
let name = item["name"].last_member().as_string();
|
|
let message = text::TextGenerator::new().generate(item)?;
|
|
messages.push(Message {
|
|
name: name,
|
|
message: message
|
|
.replace("<rt2>", "\n")
|
|
.replace("<ret2>", "\n")
|
|
.trim_end_matches("\n")
|
|
.to_string(),
|
|
});
|
|
}
|
|
}
|
|
let select = &block["select"];
|
|
if select.is_array() {
|
|
let lan = match lang {
|
|
Some(l) => l,
|
|
None => {
|
|
for l in select.kv_keys() {
|
|
if l.is_str() && l != "vo" {
|
|
lang = l.as_str();
|
|
break;
|
|
}
|
|
}
|
|
match lang {
|
|
Some(l) => l,
|
|
// No select text found, continue to next block
|
|
None => {
|
|
block_name = match block["linknext"].as_str() {
|
|
Some(name) => name,
|
|
None => break,
|
|
};
|
|
block = &ast[block_name];
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
let mut select_text = &select[lan];
|
|
if select_text.is_null() {
|
|
for l in select.kv_keys() {
|
|
if l != "vo" {
|
|
select_text = &select[l];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
for item in select_text.members() {
|
|
if let Some(select) = item.as_str() {
|
|
messages.push(Message {
|
|
name: None,
|
|
message: select.to_string(),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
block_name = match block["linknext"].as_str() {
|
|
Some(name) => name,
|
|
None => break,
|
|
};
|
|
block = &ast[block_name];
|
|
}
|
|
Ok(messages)
|
|
}
|
|
|
|
fn import_messages<'a>(
|
|
&'a self,
|
|
messages: Vec<Message>,
|
|
mut file: Box<dyn WriteSeek + 'a>,
|
|
_filename: &str,
|
|
encoding: Encoding,
|
|
replacement: Option<&'a ReplacementTable>,
|
|
) -> Result<()> {
|
|
let mut ast = self.ast.clone();
|
|
let root = &mut ast.ast;
|
|
let mut lang = self.lang.as_ref().map(|s| s.to_string());
|
|
let mut mess = messages.iter();
|
|
let mut mes = mess.next();
|
|
if root["label"]["top"]["block"].is_null() && root["text"].is_array() {
|
|
let mut text_index = 1i64;
|
|
let len = root.len();
|
|
for i in 0..len {
|
|
if root[i].is_array() {
|
|
if root[i][Key("savetitle")].is_array() {
|
|
let lan = self.lang.as_ref().map(|s| s.as_str()).unwrap_or("text");
|
|
let m = match mes {
|
|
Some(m) => m,
|
|
None => return Err(anyhow::anyhow!("Not enough messages.")),
|
|
};
|
|
let mut title = m.message.clone();
|
|
if let Some(repl) = replacement {
|
|
for (k, v) in &repl.map {
|
|
title = title.replace(k, v);
|
|
}
|
|
}
|
|
root[i][Key("savetitle")][lan].set_string(title);
|
|
mes = mess.next();
|
|
}
|
|
}
|
|
if !root[i][Key("text")].is_null() {
|
|
let lan = match &lang {
|
|
Some(l) => l.as_str(),
|
|
None => {
|
|
for l in root["text"][NumKey(text_index)].kv_keys() {
|
|
if l.is_str() && l != "vo" && l != "name" {
|
|
lang = l.as_string();
|
|
break;
|
|
}
|
|
}
|
|
match lang {
|
|
Some(ref l) => l.as_str(),
|
|
// No text found, continue to next block
|
|
None => continue,
|
|
}
|
|
}
|
|
};
|
|
if root["text"][NumKey(text_index)]["name"].is_array() {
|
|
let name = match mes {
|
|
Some(m) => m.name.clone(),
|
|
None => return Err(anyhow::anyhow!("Message name is missing.")),
|
|
};
|
|
let mut name = match name {
|
|
Some(n) => n,
|
|
None => return Err(anyhow::anyhow!("Message name is missing.")),
|
|
};
|
|
if let Some(repl) = replacement {
|
|
for (k, v) in &repl.map {
|
|
name = name.replace(k, v);
|
|
}
|
|
}
|
|
let nlan = self.lang.as_ref().map(|s| s.as_str()).unwrap_or("name");
|
|
root["text"][NumKey(text_index)]["name"][nlan].set_string(name);
|
|
}
|
|
let origin_count = {
|
|
let text = &root["text"][NumKey(text_index)];
|
|
let mut tex = &text[lan];
|
|
if tex.is_null() {
|
|
for l in text.kv_keys() {
|
|
if l != "vo" && l != "name" {
|
|
tex = &text[l];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
tex.len()
|
|
};
|
|
let mut arr = Value::new_array();
|
|
for _ in 0..origin_count {
|
|
let m = match mes {
|
|
Some(m) => m,
|
|
None => return Err(anyhow::anyhow!("Not enough messages.")),
|
|
};
|
|
let mut text = m.message.clone();
|
|
if let Some(repl) = replacement {
|
|
for (k, v) in &repl.map {
|
|
text = text.replace(k, v);
|
|
}
|
|
}
|
|
let v = text::TextParser::new(&text.replace("\n", "<rt2>")).parse()?;
|
|
arr.push_member(v);
|
|
mes = mess.next();
|
|
}
|
|
root["text"][NumKey(text_index)][lan] = arr;
|
|
text_index += 1;
|
|
} else if !root[i][Key("select")].is_null() {
|
|
let lan = match &lang {
|
|
Some(l) => l.as_str(),
|
|
None => {
|
|
for l in root["text"][NumKey(text_index)]["select"].kv_keys() {
|
|
if l.is_str() && l != "vo" && l != "name" {
|
|
lang = l.as_string();
|
|
break;
|
|
}
|
|
}
|
|
match lang {
|
|
Some(ref l) => l.as_str(),
|
|
// No text found, continue to next block
|
|
None => continue,
|
|
}
|
|
}
|
|
};
|
|
let count = {
|
|
let text = &root["text"][NumKey(text_index)]["select"];
|
|
let mut tex = &text[lan];
|
|
if tex.is_null() {
|
|
for l in text.kv_keys() {
|
|
if l != "vo" && l != "name" {
|
|
tex = &text[l];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
tex.len()
|
|
};
|
|
let mut new_select = Value::new_array();
|
|
for _ in 0..count {
|
|
let m = match mes {
|
|
Some(m) => m,
|
|
None => return Err(anyhow::anyhow!("Not enough messages.")),
|
|
};
|
|
let mut select_text = m.message.clone();
|
|
if let Some(repl) = replacement {
|
|
for (k, v) in &repl.map {
|
|
select_text = select_text.replace(k, v);
|
|
}
|
|
}
|
|
new_select.push_member(Value::Str(select_text));
|
|
mes = mess.next();
|
|
}
|
|
root["text"][NumKey(text_index)]["select"][lan] = new_select;
|
|
text_index += 1;
|
|
}
|
|
}
|
|
if mes.is_some() || mess.next().is_some() {
|
|
return Err(anyhow::anyhow!("Not all messages were used."));
|
|
}
|
|
let mut writer = Vec::new();
|
|
let mut dumper = dump::Dumper::new(&mut writer);
|
|
if self.no_indent {
|
|
dumper.set_no_indent();
|
|
} else if let Some(indent) = self.indent {
|
|
dumper.set_indent(indent);
|
|
}
|
|
dumper.set_max_line_width(self.max_line_width);
|
|
dumper.dump(&ast)?;
|
|
let data = String::from_utf8(writer)?;
|
|
let encoded = encode_string(encoding, &data, false)?;
|
|
file.write_all(&encoded)?;
|
|
file.flush()?;
|
|
return Ok(());
|
|
}
|
|
let mut block_name = root["label"]["top"]["block"]
|
|
.as_string()
|
|
.ok_or(anyhow::anyhow!("Missing top block name"))?;
|
|
let mut block = &mut root[block_name];
|
|
loop {
|
|
if block[Key("savetitle")].is_array() {
|
|
let lan = self.lang.as_ref().map(|s| s.as_str()).unwrap_or("text");
|
|
let m = match mes {
|
|
Some(m) => m,
|
|
None => return Err(anyhow::anyhow!("Not enough messages.")),
|
|
};
|
|
let mut title = m.message.clone();
|
|
if let Some(repl) = replacement {
|
|
for (k, v) in &repl.map {
|
|
title = title.replace(k, v);
|
|
}
|
|
}
|
|
block[Key("savetitle")][lan].set_string(title);
|
|
mes = mess.next();
|
|
}
|
|
if block["text"].is_array() {
|
|
let lan = match &lang {
|
|
Some(l) => l.as_str(),
|
|
None => {
|
|
for l in block["text"].kv_keys() {
|
|
if l.is_str() && l != "vo" {
|
|
lang = l.as_string();
|
|
break;
|
|
}
|
|
}
|
|
match lang {
|
|
Some(ref l) => l.as_str(),
|
|
// No text found, continue to next block
|
|
None => {
|
|
block_name = match block["linknext"].as_string() {
|
|
Some(name) => name,
|
|
None => break,
|
|
};
|
|
block = &mut root[block_name];
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
let origin_names: Vec<_> = {
|
|
let mut tex = &block["text"][lan];
|
|
if tex.is_null() {
|
|
for l in block["text"].kv_keys() {
|
|
if l != "vo" {
|
|
tex = &block["text"][l];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
tex.members().map(|m| m["name"].clone()).collect()
|
|
};
|
|
let mut arr = Value::new_array();
|
|
for name in origin_names {
|
|
let m = match mes {
|
|
Some(m) => m,
|
|
None => return Err(anyhow::anyhow!("Not enough messages.")),
|
|
};
|
|
let mut text = m.message.clone();
|
|
if let Some(repl) = replacement {
|
|
for (k, v) in &repl.map {
|
|
text = text.replace(k, v);
|
|
}
|
|
}
|
|
if !text.ends_with("\n") {
|
|
text.push('\n');
|
|
}
|
|
let mut v = text::TextParser::new(&text.replace("\n", "<rt2>")).parse()?;
|
|
if name.is_array() {
|
|
let mut n = match &m.name {
|
|
Some(n) => n.to_string(),
|
|
None => return Err(anyhow::anyhow!("Message name is missing.")),
|
|
};
|
|
if let Some(repl) = replacement {
|
|
for (k, v) in &repl.map {
|
|
n = n.replace(k, v);
|
|
}
|
|
}
|
|
v.insert_member(0, Value::new_kv("name", name));
|
|
if v["name"].len() <= 1 {
|
|
if v["name"][0] != n {
|
|
v["name"].push_member(Value::Str(n));
|
|
}
|
|
} else {
|
|
v["name"].last_member_mut().set_string(n);
|
|
}
|
|
}
|
|
arr.push_member(v);
|
|
mes = mess.next();
|
|
}
|
|
block["text"][lan] = arr;
|
|
}
|
|
if block["select"].is_array() {
|
|
let lan = match &lang {
|
|
Some(l) => l.as_str(),
|
|
None => {
|
|
for l in block["select"].kv_keys() {
|
|
if l.is_str() && l != "vo" {
|
|
lang = l.as_string();
|
|
break;
|
|
}
|
|
}
|
|
match lang {
|
|
Some(ref l) => l.as_str(),
|
|
// No text found, continue to next block
|
|
None => {
|
|
block_name = match block["linknext"].as_string() {
|
|
Some(name) => name,
|
|
None => break,
|
|
};
|
|
block = &mut root[block_name];
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
let select_count = {
|
|
let mut select = &block["select"][lan];
|
|
if select.is_null() {
|
|
for l in block["select"].kv_keys() {
|
|
if l != "vo" {
|
|
select = &block["select"][l];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
select.len()
|
|
};
|
|
let mut new_select = Value::new_array();
|
|
for _ in 0..select_count {
|
|
let m = match mes {
|
|
Some(m) => m,
|
|
None => return Err(anyhow::anyhow!("Not enough messages.")),
|
|
};
|
|
let mut select_text = m.message.clone();
|
|
if let Some(repl) = replacement {
|
|
for (k, v) in &repl.map {
|
|
select_text = select_text.replace(k, v);
|
|
}
|
|
}
|
|
new_select.push_member(Value::Str(select_text));
|
|
mes = mess.next();
|
|
}
|
|
block["select"][lan] = new_select;
|
|
}
|
|
block_name = match block["linknext"].as_string() {
|
|
Some(name) => name,
|
|
None => break,
|
|
};
|
|
block = &mut root[block_name];
|
|
}
|
|
if mes.is_some() || mess.next().is_some() {
|
|
return Err(anyhow::anyhow!("Not all messages were used."));
|
|
}
|
|
let mut writer = Vec::new();
|
|
let mut dumper = dump::Dumper::new(&mut writer);
|
|
if self.no_indent {
|
|
dumper.set_no_indent();
|
|
} else if let Some(indent) = self.indent {
|
|
dumper.set_indent(indent);
|
|
}
|
|
dumper.set_max_line_width(self.max_line_width);
|
|
dumper.dump(&ast)?;
|
|
let data = String::from_utf8(writer)?;
|
|
let encoded = encode_string(encoding, &data, false)?;
|
|
file.write_all(&encoded)?;
|
|
file.flush()?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Checks if the given buffer is in the Artemis AST format.
|
|
///
|
|
/// * `filename` - The name of the file.
|
|
/// * `buf` - The buffer containing the data.
|
|
/// * `buf_len` - The length of the buffer.
|
|
pub fn is_this_format(_filename: &str, buf: &[u8], buf_len: usize) -> bool {
|
|
let parser = parser::Parser::new(&buf[..buf_len], Encoding::Utf8);
|
|
parser.try_parse_header().is_ok()
|
|
}
|