diff --git a/README.md b/README.md index f44cae2..e802b9f 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,9 @@ msg-tool create -t |---|---|---|---|---|---| | `escude-arc` | `escude-arc` | Escu:de Archive File (.bin) | ✔️ | ✔️ | | ### Entis GLS engine +| Script Type | Feature Name | Name | Export | Import | Custom Export | Custom Import | Create | Remarks | +|---|---|---|---|---|---|---|---|---| +| `entis-gls` | `entis-gls` | Entis GLS engine XML Script (.srcxml) | ✔️ | ✔️ | ❌ | ❌ | ❌ | | ### ExHibit | Script Type | Feature Name | Name | Export | Import | Custom Export | Custom Import | Create | Remarks | |---|---|---|---|---|---|---|---|---| diff --git a/src/ext/rcdom.rs b/src/ext/rcdom.rs index bad147c..86d5ee8 100644 --- a/src/ext/rcdom.rs +++ b/src/ext/rcdom.rs @@ -2,7 +2,8 @@ use anyhow::Result; use markup5ever::Attribute; use markup5ever_rcdom::{Node, NodeData}; -use std::cell::Ref; +use std::cell::{Ref, RefCell}; +use std::rc::{Rc, Weak}; /// Extensions for [Node] pub trait NodeExt { @@ -22,6 +23,51 @@ pub trait NodeExt { /// 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>; + /// Sets the value of the attribute with the given name. + /// + /// This function creates the attribute if it does not exist. + /// This function ignores namespaces and only checks the local name of the attribute. + /// Returns `Ok(())` if the node is not an element. + fn set_attr_value + ?Sized, V: AsRef + ?Sized>( + &self, + name: &S, + value: &V, + ) -> Result<()>; +} + +/// Extensions for [Rc] +pub trait RcNodeExt { + /// Pushes a child node to the current node. + fn push_child(&self, child: Rc) -> Result<()>; + /// Create a deep clone + fn deep_clone(&self, parent: Option>) -> Result>; + /// Create a deep clone with modification of data. + fn deep_clone_with_modify Result<()>>( + &self, + parent: Option>, + modify: F, + ) -> Result>; + /// Changes a child node at the given index by modifying its data. + /// + /// Deep clones are needed. + fn change_child Result<()>>( + &self, + index: usize, + modify: F, + ) -> Result<()>; +} + +/// Extensions for [NodeData] +pub trait NodeDataExt { + /// clones the node data. + fn clone(&self) -> Result; + /// Sets the content of a processing instruction. + /// + /// Returns `Ok(())` if the node is not a processing instruction. + fn set_processing_instruction_content + ?Sized>( + &mut self, + content: &S, + ) -> Result<()>; } impl NodeExt for Node { @@ -66,6 +112,164 @@ impl NodeExt for Node { _ => Ok(None), } } + + fn set_attr_value + ?Sized, V: AsRef + ?Sized>( + &self, + name: &S, + value: &V, + ) -> Result<()> { + match &self.data { + NodeData::Element { attrs, .. } => { + let mut borrowed = attrs.try_borrow_mut()?; + if let Some(attr) = borrowed + .iter_mut() + .find(|a| a.name.local.as_ref() == name.as_ref()) + { + attr.value = value.as_ref().into(); + } else { + borrowed.push(Attribute { + name: markup5ever::QualName::new( + None, + markup5ever::Namespace::default(), + name.as_ref().into(), + ), + value: value.as_ref().into(), + }); + } + Ok(()) + } + _ => Ok(()), + } + } +} + +impl RcNodeExt for Rc { + fn push_child(&self, child: Rc) -> Result<()> { + child.parent.replace(Some(Rc::downgrade(self))); + self.children.try_borrow_mut()?.push(child); + Ok(()) + } + + fn deep_clone(&self, parent: Option>) -> Result> { + let data = self.data.clone()?; + let node = Node { + data, + children: RefCell::new(Vec::new()), + parent: parent.into(), + }; + let node = Rc::new(node); + for child in self.children.try_borrow()?.iter() { + let cloned_child = child.deep_clone(Some(Rc::downgrade(&node)))?; + node.push_child(cloned_child)?; + } + Ok(node) + } + + fn deep_clone_with_modify Result<()>>( + &self, + parent: Option>, + modify: F, + ) -> Result> { + let mut data = self.data.clone()?; + modify(&mut data)?; + let node = Node { + data, + children: RefCell::new(Vec::new()), + parent: parent.into(), + }; + let node = Rc::new(node); + for child in self.children.try_borrow()?.iter() { + let cloned_child = child.deep_clone(Some(Rc::downgrade(&node)))?; + node.push_child(cloned_child)?; + } + Ok(node) + } + + fn change_child Result<()>>( + &self, + index: usize, + modify: F, + ) -> Result<()> { + let mut children = self.children.try_borrow_mut()?; + if index >= children.len() { + return Err(anyhow::anyhow!("Index out of bounds")); + } + let child = children.remove(index); + child.parent.take(); + let nchild = child.deep_clone_with_modify(Some(Rc::downgrade(self)), modify)?; + children.insert(index, nchild); + Ok(()) + } +} + +impl NodeDataExt for NodeData { + fn clone(&self) -> Result { + Ok(match self { + NodeData::Document => NodeData::Document, + NodeData::Comment { contents } => NodeData::Comment { + contents: contents.clone(), + }, + NodeData::Doctype { + name, + public_id, + system_id, + } => NodeData::Doctype { + name: name.clone(), + public_id: public_id.clone(), + system_id: system_id.clone(), + }, + NodeData::Text { contents } => NodeData::Text { + contents: contents.clone(), + }, + NodeData::ProcessingInstruction { target, contents } => { + NodeData::ProcessingInstruction { + target: target.clone(), + contents: contents.clone(), + } + } + NodeData::Element { + name, + attrs, + template_contents, + mathml_annotation_xml_integration_point, + } => { + let name = name.clone(); + let mut nattrs = Vec::new(); + for attr in attrs.try_borrow()?.iter() { + nattrs.push(Attribute { + name: attr.name.clone(), + value: attr.value.clone(), + }); + } + let attrs = RefCell::new(nattrs); + let template = match template_contents.try_borrow()?.as_ref() { + Some(tc) => Some(tc.deep_clone(None)?), + None => None, + }; + let template_contents = RefCell::new(template); + NodeData::Element { + name, + attrs, + template_contents, + mathml_annotation_xml_integration_point: + mathml_annotation_xml_integration_point.clone(), + } + } + }) + } + + fn set_processing_instruction_content + ?Sized>( + &mut self, + content: &S, + ) -> Result<()> { + match self { + NodeData::ProcessingInstruction { contents, .. } => { + *contents = content.as_ref().into(); + Ok(()) + } + _ => Ok(()), + } + } } struct AttrKeyIter<'a> { diff --git a/src/scripts/entis_gls/srcxml.rs b/src/scripts/entis_gls/srcxml.rs index 3c4ef3c..47b250e 100644 --- a/src/scripts/entis_gls/srcxml.rs +++ b/src/scripts/entis_gls/srcxml.rs @@ -47,22 +47,19 @@ impl ScriptBuilder for SrcXmlScriptBuilder { } } +#[derive(Debug)] +/// Entis GLS engine XML Script 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 { + /// Creates a new `SrcXmlScript` from the provided buffer and encoding. + /// + /// * `buf` - The buffer containing the XML data. + /// * `encoding` - The encoding of the XML data. + /// * `config` - Additional configuration options. 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()) @@ -76,7 +73,6 @@ impl SrcXmlScript { } } Ok(Self { - decoded, handle: dom.document, lang: config.entis_gls_srcxml_lang.clone(), }) @@ -175,19 +171,127 @@ impl Script for SrcXmlScript { fn import_messages<'a>( &'a self, - _messages: Vec, + messages: Vec, mut file: Box, encoding: Encoding, - _replacement: Option<&'a ReplacementTable>, + replacement: Option<&'a ReplacementTable>, ) -> Result<()> { - let dom = parse_document(RcDom::default(), Default::default()) - .from_utf8() - .one(self.decoded.as_bytes()); - let root = dom.document; + let root = self.handle.deep_clone(None)?; 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 len = root.children.try_borrow()?.len(); + if len > 0 && root.children.try_borrow()?[0].is_processing_instruction("xml") { + root.change_child(0, |data| { + data.set_processing_instruction_content("version=\"1.0\"") + })?; + } + } + let mut lang = self.lang.clone(); + let mut mess = messages.iter(); + let mut mes = mess.next(); + for i in root.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 m = match mes { + Some(m) => m, + None => { + return Err(anyhow::anyhow!( + "Not enough messages provided" + )); + } + }; + 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; + } + } + if lang.is_none() { + lang = Some(String::new()); + } + lang.as_ref().map(|s| s.as_str()).unwrap_or("") + } + }; + let name_ref = if lan.is_empty() { + "name" + } else { + &format!("name_{}", lan) + }; + let name = match &m.name { + Some(name) => { + let mut name = name.clone(); + if let Some(repl) = replacement { + for (k, v) in &repl.map { + name = name.replace(k, v); + } + } + name + } + None => String::new(), + }; + ins.set_attr_value(name_ref, &name)?; + let message = m.message.clone(); + let text_ref = if lan.is_empty() { + "text" + } else { + &format!("text_{}", lan) + }; + ins.set_attr_value(text_ref, &message)?; + mes = mess.next(); + } else if ins.is_element("select") { + for menu in ins.children.try_borrow()?.iter() { + if menu.is_element("menu") { + let m = match mes { + Some(m) => m, + None => { + return Err(anyhow::anyhow!( + "Not enough messages provided" + )); + } + }; + 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; + } + } + if lang.is_none() { + lang = Some(String::new()); + } + lang.as_ref().map(|s| s.as_str()).unwrap_or("") + } + }; + let text_ref = if lan.is_empty() { + "text" + } else { + &format!("text_{}", lan) + }; + let mut message = m.message.clone(); + if let Some(repl) = replacement { + for (k, v) in &repl.map { + message = message.replace(k, v); + } + } + menu.set_attr_value(text_ref, &message)?; + mes = mess.next(); + } + } + } + } + } + } } } let doc: SerializableHandle = root.clone().into();