diff --git a/Cargo.lock b/Cargo.lock index 02f1ff6..22eefec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -441,6 +441,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -481,6 +491,17 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "html5ever" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55d958c2f74b664487a2035fe1dadb032c48718a03b63f3ab0b8537db8549ed4" +dependencies = [ + "log", + "markup5ever", + "match_token", +] + [[package]] name = "icu_collections" version = "2.0.0" @@ -685,6 +706,62 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "markup5ever" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "311fe69c934650f8f19652b3946075f0fc41ad8757dbb68f1ca14e7900ecc1c3" +dependencies = [ + "log", + "tendril", + "web_atoms", +] + +[[package]] +name = "markup5ever_rcdom" +version = "0.35.0+unofficial" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8bcd53df4748257345b8bc156d620340ce0f015ec1c7ef1cff475543888a31d" +dependencies = [ + "html5ever", + "markup5ever", + "tendril", + "xml5ever", +] + +[[package]] +name = "match_token" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac84fd3f360fcc43dc5f5d186f02a94192761a080e8bc58621ad4d12296a58cf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "memchr" version = "2.7.5" @@ -744,12 +821,14 @@ dependencies = [ "json", "lazy_static", "libtlg-rs", + "markup5ever", + "markup5ever_rcdom", "memchr", "mozjpeg", "msg_tool_macro", "overf", "png", - "rand", + "rand 0.9.2", "serde", "serde_json", "serde_yaml_ng", @@ -759,6 +838,7 @@ dependencies = [ "utf16string", "webp", "windows-sys", + "xml5ever", "zstd", ] @@ -781,6 +861,12 @@ dependencies = [ "jobserver", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + [[package]] name = "nix" version = "0.30.1" @@ -819,12 +905,73 @@ dependencies = [ "syn", ] +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand 0.8.5", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + [[package]] name = "pkg-config" version = "0.3.32" @@ -862,6 +1009,12 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + [[package]] name = "proc-macro2" version = "1.0.95" @@ -898,6 +1051,15 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "rand" version = "0.9.2" @@ -905,7 +1067,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha", - "rand_core", + "rand_core 0.9.3", ] [[package]] @@ -915,9 +1077,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.9.3", ] +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + [[package]] name = "rand_core" version = "0.9.3" @@ -927,6 +1095,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "redox_syscall" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +dependencies = [ + "bitflags 2.9.1", +] + [[package]] name = "regex-automata" version = "0.4.9" @@ -959,6 +1136,12 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "serde" version = "1.0.219" @@ -1027,6 +1210,12 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "smallvec" version = "1.15.1" @@ -1039,6 +1228,31 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", +] + [[package]] name = "strsim" version = "0.11.1" @@ -1067,6 +1281,17 @@ dependencies = [ "syn", ] +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + [[package]] name = "tinystr" version = "0.8.1" @@ -1112,6 +1337,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf16string" version = "0.2.0" @@ -1148,6 +1379,18 @@ dependencies = [ "wit-bindgen-rt", ] +[[package]] +name = "web_atoms" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57ffde1dc01240bdf9992e3205668b235e59421fd085e8a317ed98da0178d414" +dependencies = [ + "phf", + "phf_codegen", + "string_cache", + "string_cache_codegen", +] + [[package]] name = "webp" version = "0.3.0" @@ -1245,6 +1488,16 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +[[package]] +name = "xml5ever" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee3f1e41afb31a75aef076563b0ad3ecc24f5bd9d12a72b132222664eb76b494" +dependencies = [ + "log", + "markup5ever", +] + [[package]] name = "yoke" version = "0.8.0" diff --git a/Cargo.toml b/Cargo.toml index cc281f2..95d4a65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,8 @@ int-enum = { version = "1.2", optional = true } json = { version = "0.12", optional = true } lazy_static = "1.5.0" libtlg-rs = { version = "0.1", optional = true } +markup5ever = { version = "0.35", optional = true } +markup5ever_rcdom = { version = "0.35", optional = true } memchr = { version = "2.7", optional = true } mozjpeg = { version = "0.10", optional = true } msg_tool_macro = { version = "0.1.0" } @@ -36,12 +38,13 @@ unicode-segmentation = "1.12" url = { version = "2.5", optional = true } utf16string = "0.2" webp = { version = "0.3", default-features = false, optional = true } +xml5ever = { version = "0.35", optional = true } zstd = { version = "0.13", optional = true } [features] default = ["all-fmt", "image-jpg", "image-webp"] all-fmt = ["all-script", "all-img", "all-arc", "all-audio"] -all-script = ["artemis", "bgi", "cat-system", "circus", "escude", "ex-hibit", "hexen-haus", "kirikiri", "will-plus", "yaneurao", "yaneurao-itufuru"] +all-script = ["artemis", "bgi", "cat-system", "circus", "entis-gls", "escude", "ex-hibit", "hexen-haus", "kirikiri", "will-plus", "yaneurao", "yaneurao-itufuru"] all-img = ["bgi-img", "cat-system-img", "circus-img", "kirikiri-img"] all-arc = ["artemis-arc", "bgi-arc", "cat-system-arc", "circus-arc", "escude-arc"] all-audio = ["bgi-audio", "circus-audio"] @@ -58,6 +61,7 @@ circus = [] circus-arc = ["circus"] circus-audio = ["circus", "flate2", "int-enum", "utils-pcm"] circus-img = ["circus", "image", "flate2", "zstd"] +entis-gls = ["xml5ever", "markup5ever", "markup5ever_rcdom"] escude = ["int-enum"] escude-arc = ["escude", "rand", "utils-bit-stream"] ex-hibit = [] diff --git a/README.md b/README.md index aafae83..f44cae2 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,7 @@ msg-tool create -t | Archive Type | Feature Name | Name | Unpack | Pack | Remarks | |---|---|---|---|---|---| | `escude-arc` | `escude-arc` | Escu:de Archive File (.bin) | ✔️ | ✔️ | | +### Entis GLS engine ### ExHibit | Script Type | Feature Name | Name | Export | Import | Custom Export | Custom Import | Create | Remarks | |---|---|---|---|---|---|---|---|---| diff --git a/src/args.rs b/src/args.rs index 2b74d58..6b57986 100644 --- a/src/args.rs +++ b/src/args.rs @@ -333,6 +333,11 @@ pub struct Arg { /// Try use YAML format instead of JSON when custom exporting. /// By default, this is based on output type. But can be overridden by this option. pub custom_yaml: Option, + #[cfg(feature = "entis-gls")] + #[arg(long, global = true)] + /// Entis GLS srcxml script language, used to extract messages from srcxml script. + /// If not specified, the first language will be used. + pub entis_gls_srcxml_lang: Option, #[command(subcommand)] /// Command pub command: Command, diff --git a/src/ext/mod.rs b/src/ext/mod.rs index dacb47e..d237417 100644 --- a/src/ext/mod.rs +++ b/src/ext/mod.rs @@ -5,4 +5,6 @@ pub mod fancy_regex; pub mod io; #[cfg(feature = "emote-psb")] pub mod psb; +#[cfg(feature = "markup5ever_rcdom")] +pub mod rcdom; pub mod vec; diff --git a/src/ext/rcdom.rs b/src/ext/rcdom.rs new file mode 100644 index 0000000..bad147c --- /dev/null +++ b/src/ext/rcdom.rs @@ -0,0 +1,88 @@ +//! Extensions for markup5ever_rcdom crate. +use anyhow::Result; +use markup5ever::Attribute; +use markup5ever_rcdom::{Node, NodeData}; +use std::cell::Ref; + +/// Extensions for [Node] +pub trait NodeExt { + /// Checks if the node is an element with the given name. + /// + /// This function ignore namespaces. + fn is_element + ?Sized>(&self, name: &S) -> bool; + /// Checks if the node is a processing instruction with the given name. + fn is_processing_instruction + ?Sized>(&self, name: &S) -> bool; + /// Returns an iterator over the attribute keys of the element node. + /// + /// This function returns an empty iterator if the node is not an element. + /// Only the local names of the attributes are returned, ignoring namespaces. + fn element_attr_keys<'a>(&'a self) -> Result + 'a>>; + /// Gets the value of the attribute with the given name. + /// + /// This function returns `Ok(None)` if the node is not an element or if the attribute does not exist. + /// This function ignores namespaces and only checks the local name of the attribute. + fn get_attr_value + ?Sized>(&self, name: &S) -> Result>; +} + +impl NodeExt for Node { + fn is_element + ?Sized>(&self, name: &S) -> bool { + match &self.data { + NodeData::Element { name: ename, .. } => ename.local.as_ref() == name.as_ref(), + _ => false, + } + } + + fn is_processing_instruction + ?Sized>(&self, name: &S) -> bool { + match &self.data { + NodeData::ProcessingInstruction { target, .. } => target.as_ref() == name.as_ref(), + _ => false, + } + } + + fn element_attr_keys<'a>(&'a self) -> Result + 'a>> { + match &self.data { + NodeData::Element { attrs, .. } => { + let borrowed = attrs.try_borrow()?; + let iter = AttrKeyIter { borrowed, pos: 0 }; + Ok(Box::new(iter)) + } + _ => Ok(Box::new(std::iter::empty())), + } + } + + fn get_attr_value + ?Sized>(&self, name: &S) -> Result> { + match &self.data { + NodeData::Element { attrs, .. } => { + let borrowed = attrs.try_borrow()?; + if let Some(attr) = borrowed + .iter() + .find(|a| a.name.local.as_ref() == name.as_ref()) + { + Ok(Some(attr.value.to_string())) + } else { + Ok(None) + } + } + _ => Ok(None), + } + } +} + +struct AttrKeyIter<'a> { + borrowed: Ref<'a, Vec>, + pos: usize, +} + +impl<'a> Iterator for AttrKeyIter<'a> { + type Item = String; + + fn next(&mut self) -> Option { + if self.pos < self.borrowed.len() { + let attr = &self.borrowed[self.pos]; + self.pos += 1; + Some(attr.name.local.to_string()) + } else { + None + } + } +} diff --git a/src/main.rs b/src/main.rs index 5097f55..01728ba 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1751,6 +1751,8 @@ fn main() { .map(|s| s == types::OutputScriptType::Yaml) .unwrap_or(false) }), + #[cfg(feature = "entis-gls")] + entis_gls_srcxml_lang: arg.entis_gls_srcxml_lang.clone(), }; match &arg.command { args::Command::Export { input, output } => { diff --git a/src/scripts/entis_gls/mod.rs b/src/scripts/entis_gls/mod.rs new file mode 100644 index 0000000..9d7212f --- /dev/null +++ b/src/scripts/entis_gls/mod.rs @@ -0,0 +1,2 @@ +//! Entis GLS engine Script +pub mod srcxml; diff --git a/src/scripts/entis_gls/srcxml.rs b/src/scripts/entis_gls/srcxml.rs new file mode 100644 index 0000000..3c4ef3c --- /dev/null +++ b/src/scripts/entis_gls/srcxml.rs @@ -0,0 +1,206 @@ +//! Entis GLS engine XML Script (.srcxml) +use crate::ext::io::*; +use crate::ext::rcdom::*; +use crate::scripts::base::*; +use crate::types::*; +use crate::utils::encoding::*; +use anyhow::Result; +use markup5ever_rcdom::{Handle, RcDom, SerializableHandle}; +use xml5ever::driver::parse_document; +use xml5ever::serialize::serialize; +use xml5ever::tendril::TendrilSink; + +#[derive(Debug)] +/// A builder for Entis GLS srcxml scripts. +pub struct SrcXmlScriptBuilder {} + +impl SrcXmlScriptBuilder { + /// Creates a new instance of `SrcXmlScriptBuilder`. + pub fn new() -> Self { + Self {} + } +} + +impl ScriptBuilder for SrcXmlScriptBuilder { + fn default_encoding(&self) -> Encoding { + Encoding::Utf8 + } + + fn build_script( + &self, + buf: Vec, + _filename: &str, + encoding: Encoding, + _archive_encoding: Encoding, + config: &ExtraConfig, + _archive: Option<&Box>, + ) -> Result> { + Ok(Box::new(SrcXmlScript::new(buf, encoding, config)?)) + } + + fn extensions(&self) -> &'static [&'static str] { + &["srcxml"] + } + + fn script_type(&self) -> &'static ScriptType { + &ScriptType::EntisGls + } +} + +pub struct SrcXmlScript { + decoded: String, + handle: Handle, + lang: Option, +} + +impl std::fmt::Debug for SrcXmlScript { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("SrcXmlScript") + .field("handle", &self.handle) + .field("lang", &self.lang) + .finish() + } +} + +impl SrcXmlScript { + pub fn new(buf: Vec, encoding: Encoding, config: &ExtraConfig) -> Result { + let decoded = decode_to_string(encoding, &buf, false)?; + let dom = parse_document(RcDom::default(), Default::default()) + .from_utf8() + .one(decoded.as_bytes()); + { + let error = dom.errors.try_borrow()?; + for e in error.iter() { + eprintln!("WARN: Error parsing srcxml: {}", e); + crate::COUNTER.inc_warning(); + } + } + Ok(Self { + decoded, + handle: dom.document, + lang: config.entis_gls_srcxml_lang.clone(), + }) + } +} + +impl Script for SrcXmlScript { + fn default_output_script_type(&self) -> OutputScriptType { + OutputScriptType::Json + } + + fn default_format_type(&self) -> FormatOptions { + FormatOptions::None + } + + fn extract_messages(&self) -> Result> { + let mut messages = Vec::new(); + let mut lang = self.lang.clone(); + for i in self.handle.children.try_borrow()?.iter() { + if i.is_element("xscript") { + for code in i.children.try_borrow()?.iter() { + if code.is_element("code") { + for ins in code.children.try_borrow()?.iter() { + if ins.is_element("msg") { + let lan = match lang.as_ref() { + Some(l) => l.as_str(), + None => { + for attr in ins.element_attr_keys()? { + if attr.starts_with("name_") + || attr.starts_with("text_") + { + lang = Some(attr[5..].to_string()); + break; + } + } + lang.as_ref().map(|s| s.as_str()).unwrap_or("") + } + }; + let name_ref = if lan.is_empty() { + "name" + } else { + &format!("name_{}", lan) + }; + let mut name = ins.get_attr_value(name_ref)?; + if name.as_ref().is_some_and(|s| s.is_empty()) { + name = None; + } + let text_ref = if lan.is_empty() { + "text" + } else { + &format!("text_{}", lan) + }; + let message = ins + .get_attr_value(text_ref)? + .ok_or(anyhow::anyhow!("text not found"))?; + messages.push(Message { name, message }) + } else if ins.is_element("select") { + for menu in ins.children.try_borrow()?.iter() { + if menu.is_element("menu") { + let lan = match lang.as_ref() { + Some(l) => l.as_str(), + None => { + for attr in ins.element_attr_keys()? { + if attr.starts_with("name_") + || attr.starts_with("text_") + { + lang = Some(attr[5..].to_string()); + break; + } + } + lang.as_ref().map(|s| s.as_str()).unwrap_or("") + } + }; + let text_ref = if lan.is_empty() { + "text" + } else { + &format!("text_{}", lan) + }; + let message = menu + .get_attr_value(text_ref)? + .ok_or(anyhow::anyhow!("text not found"))?; + messages.push(Message { + name: None, + message, + }); + } + } + } + } + } + } + } + } + Ok(messages) + } + + fn import_messages<'a>( + &'a self, + _messages: Vec, + mut file: Box, + encoding: Encoding, + _replacement: Option<&'a ReplacementTable>, + ) -> Result<()> { + let dom = parse_document(RcDom::default(), Default::default()) + .from_utf8() + .one(self.decoded.as_bytes()); + let root = dom.document; + if !encoding.is_utf8() { + let mut childrens = root.children.try_borrow_mut()?; + if childrens.len() > 1 && childrens[0].is_processing_instruction("xml") { + childrens.remove(0); + } + } + let doc: SerializableHandle = root.clone().into(); + let mut output = MemWriter::new(); + serialize(&mut output, &doc, Default::default()) + .map_err(|e| anyhow::anyhow!("Error serializing srcxml: {}", e))?; + if encoding.is_utf8() { + file.write_all(&output.data)?; + return Ok(()); + } + let s = decode_to_string(Encoding::Utf8, &output.data, true)?; + let encoded = encode_string(encoding, &s, false)?; + file.write_all(&encoded)?; + Ok(()) + } +} diff --git a/src/scripts/mod.rs b/src/scripts/mod.rs index c89ad2f..5cc117b 100644 --- a/src/scripts/mod.rs +++ b/src/scripts/mod.rs @@ -8,6 +8,8 @@ pub mod bgi; pub mod cat_system; #[cfg(feature = "circus")] pub mod circus; +#[cfg(feature = "entis-gls")] +pub mod entis_gls; #[cfg(feature = "escude")] pub mod escude; #[cfg(feature = "ex-hibit")] @@ -102,6 +104,8 @@ lazy_static::lazy_static! { Box::new(circus::image::crxd::CrxdImageBuilder::new()), #[cfg(feature = "bgi-audio")] Box::new(bgi::audio::audio::BgiAudioBuilder::new()), + #[cfg(feature = "entis-gls")] + Box::new(entis_gls::srcxml::SrcXmlScriptBuilder::new()), ]; /// A list of all script extensions. pub static ref ALL_EXTS: Vec = diff --git a/src/types.rs b/src/types.rs index cdaef59..3f7f01c 100644 --- a/src/types.rs +++ b/src/types.rs @@ -36,6 +36,16 @@ impl Encoding { _ => false, } } + + /// Returns true if the encoding is UTF8. + pub fn is_utf8(&self) -> bool { + match self { + Self::Utf8 => true, + #[cfg(windows)] + Self::CodePage(code_page) => *code_page == 65001, + _ => false, + } + } } #[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)] @@ -325,6 +335,10 @@ pub struct ExtraConfig { pub circus_crx_canvas: bool, /// Try use YAML format instead of JSON when custom exporting. pub custom_yaml: bool, + #[cfg(feature = "entis-gls")] + /// Entis GLS srcxml script language, used to extract messages from srcxml script. + /// If not specified, the first language will be used. + pub entis_gls_srcxml_lang: Option, } #[derive(Clone, Copy, Debug, ValueEnum, PartialEq, Eq, PartialOrd, Ord)] @@ -409,6 +423,9 @@ pub enum ScriptType { #[cfg(feature = "circus-img")] /// Circus Differential Image CircusCrxd, + #[cfg(feature = "entis-gls")] + /// Entis GLS srcxml Script + EntisGls, #[cfg(feature = "escude-arc")] /// Escude bin archive EscudeArc,