Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ edition = "2021"
[dependencies]
encoding_rs_io = "0.1.7"
rayon = "1.11.0"
quick-xml = "0.37.5"
quick-xml = "0.39.0"
regex = "1.12.2"
clap = { version = "4.5.54", features = ["derive"] }
anyhow = "1.0.100"
Expand Down
11 changes: 9 additions & 2 deletions src/script_sanitizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use crate::script_steps::sanitizer::sanitize;
use crate::utils::attributes::get_attribute;
use crate::utils::write_text_file;
use crate::utils::xml_utils::{
cdata_element_to_string, end_element_to_string, local_name_to_string, start_element_to_string,
text_element_to_string,
cdata_element_to_string, end_element_to_string, general_ref_to_string, local_name_to_string,
start_element_to_string, text_element_to_string,
};

#[derive(Debug, Default)]
Expand Down Expand Up @@ -224,6 +224,13 @@ fn parse_script_xml(xml_content: &str, flags: &Flags) -> Option<ScriptInfo> {
.push_str(text_element_to_string(&e, true).as_str());
}
}
Ok(Event::GeneralRef(e)) => {
if in_step {
step_info
.content
.push_str(general_ref_to_string(&e, true).as_str());
}
}
_ => {}
}

Expand Down
7 changes: 5 additions & 2 deletions src/script_steps/parameters/layout_reference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use quick_xml::Reader;

use crate::script_steps::parameters::calculation::Calculation;
use crate::utils::attributes::get_attribute;
use crate::utils::xml_utils::text_to_string;
use crate::utils::xml_utils::{general_ref_to_string, text_to_string};

#[derive(Debug, Default)]
pub struct LayoutReferenceContainer {
Expand All @@ -20,7 +20,10 @@ impl LayoutReferenceContainer {
Err(_) => continue,
Ok(Event::Eof) => break,
Ok(Event::Text(e)) => {
label = text_to_string(&e);
label.push_str(&text_to_string(&e));
}
Ok(Event::GeneralRef(e)) => {
label.push_str(&general_ref_to_string(&e, false));
}
Ok(Event::End(_)) => break,
_ => {}
Expand Down
5 changes: 4 additions & 1 deletion src/supporting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::io::{BufRead, Read};
use quick_xml::events::{BytesStart, Event};

use crate::utils::xml_utils::{
cdata_element_to_string, end_element_to_string, start_element_to_string,
cdata_element_to_string, end_element_to_string, general_ref_to_string, start_element_to_string,
text_element_to_string, XmlEventType,
};
use crate::utils::{build_out_dir_path, create_dir, push_line_to_skeleton, write_xml_file};
Expand Down Expand Up @@ -95,6 +95,9 @@ pub fn process_supporting_element<R: Read + BufRead>(
}
result.push_str(text_string.as_str());
}
Ok(Event::GeneralRef(e)) => {
result.push_str(general_ref_to_string(&e, true).as_str());
}
Ok(Event::Comment(e)) => {
result.push_str(text_element_to_string(&e, false).as_str());
}
Expand Down
10 changes: 6 additions & 4 deletions src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ use crate::utils::attributes::get_attributes;
use crate::utils::file_utils::{escape_filename, join_scope_id_and_name, should_skip_line};
use crate::utils::xml_utils::{
element_to_string, encode_xml_special_characters, end_element_to_string,
extract_values_from_xml_paths, start_element_to_string, text_element_to_string, XmlEventType,
extract_values_from_xml_paths, general_ref_to_string, start_element_to_string,
text_element_to_string, XmlEventType,
};
use crate::xml_processor::{Action, ProcessingContext, Qualifier, TopLevelSection};
use crate::{OutputTree, Skeleton};
Expand Down Expand Up @@ -107,14 +108,15 @@ impl Entity {
Ok(Event::Text(e)) => {
self.content += text_element_to_string(&e, false).as_str();
}
Ok(Event::GeneralRef(e)) => {
self.content += general_ref_to_string(&e, false).as_str();
}
Ok(Event::End(e)) => {
self.content += end_element_to_string(&e).as_str();
break;
}
Ok(Event::Eof) => break,
unknown_event => {
panic!("Wrong read event: {unknown_event:?}");
}
_ => {}
};
buf.clear();
}
Expand Down
95 changes: 87 additions & 8 deletions src/utils/xml_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::io::{BufRead, BufReader, Read};
use std::path::Path;

use anyhow::Result;
use quick_xml::events::{BytesCData, BytesEnd, BytesStart, BytesText, Event};
use quick_xml::events::{BytesCData, BytesEnd, BytesRef, BytesStart, BytesText, Event};
use quick_xml::Reader;

use crate::utils::attributes::get_attributes;
Expand Down Expand Up @@ -86,8 +86,10 @@ pub fn local_name_to_string(local_name: &[u8]) -> String {
}

pub fn text_to_string(e: &BytesText) -> String {
match e.unescape() {
Ok(text) => text.to_string(),
// In quick-xml 0.39+, entity references are delivered as separate Event::GeneralRef,
// so Event::Text no longer contains entities and we just need to decode
match e.decode() {
Ok(decoded) => decoded.to_string(),
Err(_) => String::new(),
}
}
Expand All @@ -99,6 +101,34 @@ pub fn cdata_to_string(e: &BytesCData) -> String {
}
}

/// Convert a general entity reference back to its escaped XML form
/// e.g., BytesRef containing "quot" -> "&quot;", BytesRef containing "#09" -> "&#09;"
pub fn general_ref_to_string(e: &BytesRef, escape: bool) -> String {
if escape {
// Keep the reference in its escaped form (e.g., &quot;, &#09;)
match e.decode() {
Ok(entity_name) => format!("&{entity_name};"),
Err(_) => String::new(),
}
} else {
// Resolve the reference to its actual character
// First try character references (e.g., &#65; or &#x41;)
if let Ok(Some(ch)) = e.resolve_char_ref() {
return ch.to_string();
}
// For named entity references, resolve using unescape
match e.decode() {
Ok(entity_name) => {
let escaped = format!("&{entity_name};");
quick_xml::escape::unescape(&escaped)
.map(|s| s.to_string())
.unwrap_or(escaped)
}
Err(_) => String::new(),
}
}
}

pub fn encode_xml_special_characters(input: String) -> String {
input
.replace("&amp;", "&")
Expand Down Expand Up @@ -201,6 +231,16 @@ pub fn push_rest_of_element_to_skeleton<R: Read + BufRead>(
XmlEventType::Text,
);
}
Ok(Event::GeneralRef(e)) => {
push_line_to_skeleton(
skeleton,
base_depth,
depth,
general_ref_to_string(&e, true).as_str(),
false,
XmlEventType::Text,
);
}
Ok(Event::Comment(e)) => {
push_line_to_skeleton(
skeleton,
Expand Down Expand Up @@ -238,6 +278,9 @@ pub fn element_to_string<R: Read + BufRead>(
Ok(Event::Text(e)) | Ok(Event::Comment(e)) => {
content.push_str(&text_element_to_string(&e, true));
}
Ok(Event::GeneralRef(e)) => {
content.push_str(&general_ref_to_string(&e, true));
}
Ok(Event::End(e)) => {
content.push_str(&end_element_to_string(&e));
depth -= 1;
Expand Down Expand Up @@ -348,6 +391,21 @@ pub fn extract_values_from_xml_paths(
}

Ok(Event::End(_)) => {
// Mark paths as resolved if we collected text content for them
for (i, path) in parsed_paths.iter().enumerate() {
if resolved_indices.contains(&i) {
continue;
}
if let Some(last_segment) = path.last() {
if !last_segment.starts_with('@')
&& path.len() == current_path.len()
&& path.iter().zip(&current_path).all(|(a, b)| *a == b)
&& results[i].is_some()
{
resolved_indices.insert(i);
}
}
}
current_path.pop();
}

Expand All @@ -362,20 +420,41 @@ pub fn extract_values_from_xml_paths(
&& path.len() == current_path.len()
&& path.iter().zip(&current_path).all(|(a, b)| *a == b)
{
match e.unescape() {
Ok(text) => {
results[i] = Some(text.to_string());
resolved_indices.insert(i);
match e.decode() {
Ok(decoded) => {
// Append to existing result or create new one
let entry = results[i].get_or_insert_with(String::new);
entry.push_str(&decoded);
}
Err(err) => {
return Err(format!("Failed to unescape text: {err}"));
return Err(format!("Failed to decode text: {err}"));
}
}
}
}
}
}

Ok(Event::GeneralRef(e)) => {
for (i, path) in parsed_paths.iter().enumerate() {
if resolved_indices.contains(&i) {
continue;
}

if let Some(last_segment) = path.last() {
if !last_segment.starts_with('@')
&& path.len() == current_path.len()
&& path.iter().zip(&current_path).all(|(a, b)| *a == b)
{
// Resolve the entity reference to its character
let resolved = general_ref_to_string(&e, false);
let entry = results[i].get_or_insert_with(String::new);
entry.push_str(&resolved);
}
}
}
}

Ok(Event::CData(e)) => {
for (i, path) in parsed_paths.iter().enumerate() {
if resolved_indices.contains(&i) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
source: src/tests.rs
expression: output_content
---

# 2025-06-23 Mislav Kos
# Copied "All script steps and all options" script from https://github.com/mrwatson-de/fmSyntaxColorizer to "Script from fmSyntaxColorizer" folder
# Updated "Miscellaneous" script to include multiple variants of the Send Mail step
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
source: src/tests.rs
expression: output_content
---

Set Error Capture [ ON ]
Go to Layout [ Layout: "My Layout for TestTable" ; Animation: None ]
Show All Records
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
source: src/tests.rs
expression: output_content
---

Set Field [ TestTable::ContainerField1 ; Base64Decode ( TestTable::TextField1 ; "image.svg" ) ]

Perform Script [ From list ; "noop" ; Parameter: "These aren't the droids you're looking for" ]
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
source: src/tests.rs
expression: output_content
---

Allow User Abort [ OFF ]

Open Transaction [ Collapsed: OFF ; Skip auto-enter options: OFF ; Skip data entry validation: OFF ; Override ESS locking conflicts: OFF ]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
source: src/tests.rs
expression: output_content
---

Clear [ Select: ON ]
Copy [ Select: ON ]
Cut [ Select: ON ]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
source: src/tests.rs
expression: output_content
---

Export Field Contents [ Create folders: ON ]
Insert Audio/Video [ Store only a reference: OFF ]
Insert Calculated Result [ Select: ON ]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
source: src/tests.rs
expression: output_content
---

Allow Formatting Bar [ OFF ]
AVPlayer Play [ ⚠️ PARAMETER "Source" NOT PARSED ⚠️ ]
AVPlayer Set Options
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
source: src/tests.rs
expression: output_content
---


Close Popover
Enter Browse Mode [ Pause: OFF ]
Enter Find Mode [ Pause: ON ]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
source: src/tests.rs
expression: output_content
---

Commit Records/Requests [ With dialog: OFF ]
Copy All Records/Requests
Copy Record/Request
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ expression: output_content
<Chunk type="VariableReference">vTags</Chunk>
<Chunk type="NoRef"> ) ) ;&#13;</Chunk>
<Chunk type="VariableReference">vTag</Chunk>
<Chunk type="NoRef"> &amp; &quot; &quot; &amp; </Chunk>
<Chunk type="NoRef"> &amp; &quot;&#09;&quot; &amp; </Chunk>
<Chunk type="FunctionRef">MBS</Chunk>
<Chunk type="NoRef">( &quot;SyntaxColoring.GetTag&quot; ; </Chunk>
<Chunk type="VariableReference">vTag</Chunk>
Expand Down
Loading