mirror of
https://github.com/lifegpc/msg-tool.git
synced 2026-06-15 01:24:22 +08:00
Add support to Entis GLS engine XML Script
This commit is contained in:
@@ -134,6 +134,9 @@ msg-tool create -t <script-type> <input> <output>
|
|||||||
|---|---|---|---|---|---|
|
|---|---|---|---|---|---|
|
||||||
| `escude-arc` | `escude-arc` | Escu:de Archive File (.bin) | ✔️ | ✔️ | |
|
| `escude-arc` | `escude-arc` | Escu:de Archive File (.bin) | ✔️ | ✔️ | |
|
||||||
### Entis GLS engine
|
### 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
|
### ExHibit
|
||||||
| Script Type | Feature Name | Name | Export | Import | Custom Export | Custom Import | Create | Remarks |
|
| Script Type | Feature Name | Name | Export | Import | Custom Export | Custom Import | Create | Remarks |
|
||||||
|---|---|---|---|---|---|---|---|---|
|
|---|---|---|---|---|---|---|---|---|
|
||||||
|
|||||||
206
src/ext/rcdom.rs
206
src/ext/rcdom.rs
@@ -2,7 +2,8 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use markup5ever::Attribute;
|
use markup5ever::Attribute;
|
||||||
use markup5ever_rcdom::{Node, NodeData};
|
use markup5ever_rcdom::{Node, NodeData};
|
||||||
use std::cell::Ref;
|
use std::cell::{Ref, RefCell};
|
||||||
|
use std::rc::{Rc, Weak};
|
||||||
|
|
||||||
/// Extensions for [Node]
|
/// Extensions for [Node]
|
||||||
pub trait NodeExt {
|
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 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.
|
/// This function ignores namespaces and only checks the local name of the attribute.
|
||||||
fn get_attr_value<S: AsRef<str> + ?Sized>(&self, name: &S) -> Result<Option<String>>;
|
fn get_attr_value<S: AsRef<str> + ?Sized>(&self, name: &S) -> Result<Option<String>>;
|
||||||
|
/// 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<S: AsRef<str> + ?Sized, V: AsRef<str> + ?Sized>(
|
||||||
|
&self,
|
||||||
|
name: &S,
|
||||||
|
value: &V,
|
||||||
|
) -> Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extensions for [Rc<Node>]
|
||||||
|
pub trait RcNodeExt {
|
||||||
|
/// Pushes a child node to the current node.
|
||||||
|
fn push_child(&self, child: Rc<Node>) -> Result<()>;
|
||||||
|
/// Create a deep clone
|
||||||
|
fn deep_clone(&self, parent: Option<Weak<Node>>) -> Result<Rc<Node>>;
|
||||||
|
/// Create a deep clone with modification of data.
|
||||||
|
fn deep_clone_with_modify<F: Fn(&mut NodeData) -> Result<()>>(
|
||||||
|
&self,
|
||||||
|
parent: Option<Weak<Node>>,
|
||||||
|
modify: F,
|
||||||
|
) -> Result<Rc<Node>>;
|
||||||
|
/// Changes a child node at the given index by modifying its data.
|
||||||
|
///
|
||||||
|
/// Deep clones are needed.
|
||||||
|
fn change_child<F: Fn(&mut NodeData) -> Result<()>>(
|
||||||
|
&self,
|
||||||
|
index: usize,
|
||||||
|
modify: F,
|
||||||
|
) -> Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extensions for [NodeData]
|
||||||
|
pub trait NodeDataExt {
|
||||||
|
/// clones the node data.
|
||||||
|
fn clone(&self) -> Result<NodeData>;
|
||||||
|
/// Sets the content of a processing instruction.
|
||||||
|
///
|
||||||
|
/// Returns `Ok(())` if the node is not a processing instruction.
|
||||||
|
fn set_processing_instruction_content<S: AsRef<str> + ?Sized>(
|
||||||
|
&mut self,
|
||||||
|
content: &S,
|
||||||
|
) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NodeExt for Node {
|
impl NodeExt for Node {
|
||||||
@@ -66,6 +112,164 @@ impl NodeExt for Node {
|
|||||||
_ => Ok(None),
|
_ => Ok(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_attr_value<S: AsRef<str> + ?Sized, V: AsRef<str> + ?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<Node> {
|
||||||
|
fn push_child(&self, child: Rc<Node>) -> Result<()> {
|
||||||
|
child.parent.replace(Some(Rc::downgrade(self)));
|
||||||
|
self.children.try_borrow_mut()?.push(child);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deep_clone(&self, parent: Option<Weak<Node>>) -> Result<Rc<Node>> {
|
||||||
|
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<F: Fn(&mut NodeData) -> Result<()>>(
|
||||||
|
&self,
|
||||||
|
parent: Option<Weak<Node>>,
|
||||||
|
modify: F,
|
||||||
|
) -> Result<Rc<Node>> {
|
||||||
|
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<F: Fn(&mut NodeData) -> 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<Self> {
|
||||||
|
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<S: AsRef<str> + ?Sized>(
|
||||||
|
&mut self,
|
||||||
|
content: &S,
|
||||||
|
) -> Result<()> {
|
||||||
|
match self {
|
||||||
|
NodeData::ProcessingInstruction { contents, .. } => {
|
||||||
|
*contents = content.as_ref().into();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
_ => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AttrKeyIter<'a> {
|
struct AttrKeyIter<'a> {
|
||||||
|
|||||||
@@ -47,22 +47,19 @@ impl ScriptBuilder for SrcXmlScriptBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// Entis GLS engine XML Script
|
||||||
pub struct SrcXmlScript {
|
pub struct SrcXmlScript {
|
||||||
decoded: String,
|
|
||||||
handle: Handle,
|
handle: Handle,
|
||||||
lang: Option<String>,
|
lang: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
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<u8>, encoding: Encoding, config: &ExtraConfig) -> Result<Self> {
|
pub fn new(buf: Vec<u8>, encoding: Encoding, config: &ExtraConfig) -> Result<Self> {
|
||||||
let decoded = decode_to_string(encoding, &buf, false)?;
|
let decoded = decode_to_string(encoding, &buf, false)?;
|
||||||
let dom = parse_document(RcDom::default(), Default::default())
|
let dom = parse_document(RcDom::default(), Default::default())
|
||||||
@@ -76,7 +73,6 @@ impl SrcXmlScript {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
decoded,
|
|
||||||
handle: dom.document,
|
handle: dom.document,
|
||||||
lang: config.entis_gls_srcxml_lang.clone(),
|
lang: config.entis_gls_srcxml_lang.clone(),
|
||||||
})
|
})
|
||||||
@@ -175,19 +171,127 @@ impl Script for SrcXmlScript {
|
|||||||
|
|
||||||
fn import_messages<'a>(
|
fn import_messages<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
_messages: Vec<Message>,
|
messages: Vec<Message>,
|
||||||
mut file: Box<dyn WriteSeek + 'a>,
|
mut file: Box<dyn WriteSeek + 'a>,
|
||||||
encoding: Encoding,
|
encoding: Encoding,
|
||||||
_replacement: Option<&'a ReplacementTable>,
|
replacement: Option<&'a ReplacementTable>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let dom = parse_document(RcDom::default(), Default::default())
|
let root = self.handle.deep_clone(None)?;
|
||||||
.from_utf8()
|
|
||||||
.one(self.decoded.as_bytes());
|
|
||||||
let root = dom.document;
|
|
||||||
if !encoding.is_utf8() {
|
if !encoding.is_utf8() {
|
||||||
let mut childrens = root.children.try_borrow_mut()?;
|
let len = root.children.try_borrow()?.len();
|
||||||
if childrens.len() > 1 && childrens[0].is_processing_instruction("xml") {
|
if len > 0 && root.children.try_borrow()?[0].is_processing_instruction("xml") {
|
||||||
childrens.remove(0);
|
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();
|
let doc: SerializableHandle = root.clone().into();
|
||||||
|
|||||||
Reference in New Issue
Block a user