This is an automated email from the ASF dual-hosted git repository.

bamaer pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git


The following commit(s) were added to refs/heads/main by this push:
     new 1535ba3254 data format mask is not inherited correctly, fixes #8956 
(#8959)
1535ba3254 is described below

commit 1535ba3254b2e86bec4f85ed8bde01ea1423ad02
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Fri Oct 16 14:13:51 2026 +0200

    data format mask is not inherited correctly, fixes #8956 (#8959)
---
 ...ext-file-output-inherited-formats-read-back.hpl | 262 +++++++++++++
 .../0123-text-file-output-inherited-formats.hpl    | 418 +++++++++++++++++++++
 .../datasets/golden-inherited-formats.csv          |   3 +
 .../transforms/files/inherited-formats.csv         |   3 +
 ...ain-0123-text-file-output-inherited-formats.hwf | 115 ++++++
 .../metadata/dataset/0108-data-set-output.json     |   1 +
 .../metadata/dataset/golden-inherited-formats.json |  63 ++++
 ...le-output-inherited-formats-read-back UNIT.json |  52 +++
 .../textfileoutput/TextFileOutputMeta.java         |   2 +-
 .../TextFileOutputFormatInheritanceTest.java       | 110 ++++++
 10 files changed, 1028 insertions(+), 1 deletion(-)

diff --git 
a/integration-tests/transforms/0123-text-file-output-inherited-formats-read-back.hpl
 
b/integration-tests/transforms/0123-text-file-output-inherited-formats-read-back.hpl
new file mode 100644
index 0000000000..2c6dc09097
--- /dev/null
+++ 
b/integration-tests/transforms/0123-text-file-output-inherited-formats-read-back.hpl
@@ -0,0 +1,262 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+-->
+<pipeline>
+  <info>
+    <pipeline_version/>
+    <capture_transform_performance>N</capture_transform_performance>
+    
<transform_performance_capturing_delay>1000</transform_performance_capturing_delay>
+    
<transform_performance_capturing_size_limit>100</transform_performance_capturing_size_limit>
+    <pipeline_type>Normal</pipeline_type>
+    <pipeline_status>0</pipeline_status>
+    <parameters/>
+    <name>0123-text-file-output-inherited-formats-read-back</name>
+    <name_sync_with_filename>Y</name_sync_with_filename>
+    <description>Reads the file written by 
0123-text-file-output-inherited-formats as plain text so the unit test can 
compare every written value with the expected formatting.</description>
+    <extended_description/>
+    <created_user>-</created_user>
+    <modified_user>-</modified_user>
+    <created_date>2026/10/16 12:00:00.000</created_date>
+    <modified_date>2026/10/16 12:00:00.000</modified_date>
+  </info>
+  <transform>
+    <type>TextFileInput2</type>
+    <name>Read written text</name>
+    <file_type>CSV</file_type>
+    <compression>None</compression>
+    <separator>;</separator>
+    <enclosure>"</enclosure>
+    <enclosure_breaks>N</enclosure_breaks>
+    <escapechar/>
+    <header>Y</header>
+    <prependFileName>N</prependFileName>
+    <nr_headerlines>1</nr_headerlines>
+    <footer>N</footer>
+    <nr_footerlines>1</nr_footerlines>
+    <line_wrapped>N</line_wrapped>
+    <nr_wraps>1</nr_wraps>
+    <layout_paged>N</layout_paged>
+    <nr_lines_per_page>80</nr_lines_per_page>
+    <nr_lines_doc_header>0</nr_lines_doc_header>
+    <noempty>Y</noempty>
+    <include>N</include>
+    <include_field/>
+    <rownum>N</rownum>
+    <rownum_field/>
+    <rownumByFile>N</rownumByFile>
+    <format>mixed</format>
+    <encoding>UTF-8</encoding>
+    <limit>0</limit>
+    <date_format_lenient>N</date_format_lenient>
+    <date_format_locale>en_US</date_format_locale>
+    <length>Characters</length>
+    <filters/>
+    <error_count_field/>
+    <error_fields_field/>
+    <error_text_field/>
+    <error_line_skipped>N</error_line_skipped>
+    <schema_definition/>
+    <ignore_fields>N</ignore_fields>
+    <file>
+      <name>${java.io.tmpdir}/0123/inherited-formats-out.csv</name>
+      <filemask/>
+      <exclude_filemask/>
+      <file_required>Y</file_required>
+      <include_subfolders>N</include_subfolders>
+      <type>CSV</type>
+      <compression>None</compression>
+    </file>
+    <files>
+      <file>
+        <name>${java.io.tmpdir}/0123/inherited-formats-out.csv</name>
+        <filemask/>
+        <exclude_filemask/>
+        <file_required>Y</file_required>
+        <include_subfolders>N</include_subfolders>
+      </file>
+    </files>
+    <accept_filenames>N</accept_filenames>
+    <accept_transform_name/>
+    <passing_through_fields>N</passing_through_fields>
+    <accept_field/>
+    <add_to_result_filenames>N</add_to_result_filenames>
+    <error_ignored>N</error_ignored>
+    <file_error_field/>
+    <file_error_message_field/>
+    <skip_bad_files>N</skip_bad_files>
+    <bad_line_files_destination_directory/>
+    <bad_line_files_extension>warning</bad_line_files_extension>
+    <error_line_files_destination_directory/>
+    <error_line_files_extension>error</error_line_files_extension>
+    <line_number_files_destination_directory/>
+    <line_number_files_extension>line</line_number_files_extension>
+    <fields>
+      <field>
+        <name>id</name>
+        <position>-1</position>
+        <length>-1</length>
+        <type>String</type>
+        <ignore>N</ignore>
+        <format/>
+        <trim_type>none</trim_type>
+        <precision>-1</precision>
+        <currency/>
+        <decimal/>
+        <group/>
+        <repeat>N</repeat>
+        <null_string/>
+        <if_null_value/>
+      </field>
+      <field>
+        <name>d_input</name>
+        <position>-1</position>
+        <length>-1</length>
+        <type>String</type>
+        <ignore>N</ignore>
+        <format/>
+        <trim_type>none</trim_type>
+        <precision>-1</precision>
+        <currency/>
+        <decimal/>
+        <group/>
+        <repeat>N</repeat>
+        <null_string/>
+        <if_null_value/>
+      </field>
+      <field>
+        <name>d_select</name>
+        <position>-1</position>
+        <length>-1</length>
+        <type>String</type>
+        <ignore>N</ignore>
+        <format/>
+        <trim_type>none</trim_type>
+        <precision>-1</precision>
+        <currency/>
+        <decimal/>
+        <group/>
+        <repeat>N</repeat>
+        <null_string/>
+        <if_null_value/>
+      </field>
+      <field>
+        <name>d_override</name>
+        <position>-1</position>
+        <length>-1</length>
+        <type>String</type>
+        <ignore>N</ignore>
+        <format/>
+        <trim_type>none</trim_type>
+        <precision>-1</precision>
+        <currency/>
+        <decimal/>
+        <group/>
+        <repeat>N</repeat>
+        <null_string/>
+        <if_null_value/>
+      </field>
+      <field>
+        <name>d_default</name>
+        <position>-1</position>
+        <length>-1</length>
+        <type>String</type>
+        <ignore>N</ignore>
+        <format/>
+        <trim_type>none</trim_type>
+        <precision>-1</precision>
+        <currency/>
+        <decimal/>
+        <group/>
+        <repeat>N</repeat>
+        <null_string/>
+        <if_null_value/>
+      </field>
+      <field>
+        <name>n_input</name>
+        <position>-1</position>
+        <length>-1</length>
+        <type>String</type>
+        <ignore>N</ignore>
+        <format/>
+        <trim_type>none</trim_type>
+        <precision>-1</precision>
+        <currency/>
+        <decimal/>
+        <group/>
+        <repeat>N</repeat>
+        <null_string/>
+        <if_null_value/>
+      </field>
+      <field>
+        <name>s_value</name>
+        <position>-1</position>
+        <length>-1</length>
+        <type>String</type>
+        <ignore>N</ignore>
+        <format/>
+        <trim_type>none</trim_type>
+        <precision>-1</precision>
+        <currency/>
+        <decimal/>
+        <group/>
+        <repeat>N</repeat>
+        <null_string/>
+        <if_null_value/>
+      </field>
+    </fields>
+    <distribute>Y</distribute>
+    <copies>1</copies>
+    <GUI>
+      <xloc>128</xloc>
+      <yloc>96</yloc>
+    </GUI>
+    <description/>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <attributes/>
+  </transform>
+  <transform>
+    <type>Dummy</type>
+    <name>OUTPUT</name>
+    <distribute>Y</distribute>
+    <copies>1</copies>
+    <GUI>
+      <xloc>352</xloc>
+      <yloc>96</yloc>
+    </GUI>
+    <description/>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <attributes/>
+  </transform>
+  <order>
+    <hop>
+      <from>Read written text</from>
+      <to>OUTPUT</to>
+      <enabled>Y</enabled>
+    </hop>
+  </order>
+  <notepads/>
+  <attributes/>
+  <transform_error_handling/>
+</pipeline>
diff --git 
a/integration-tests/transforms/0123-text-file-output-inherited-formats.hpl 
b/integration-tests/transforms/0123-text-file-output-inherited-formats.hpl
new file mode 100644
index 0000000000..ac53063eb2
--- /dev/null
+++ b/integration-tests/transforms/0123-text-file-output-inherited-formats.hpl
@@ -0,0 +1,418 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+-->
+<pipeline>
+  <info>
+    <pipeline_version/>
+    <capture_transform_performance>N</capture_transform_performance>
+    
<transform_performance_capturing_delay>1000</transform_performance_capturing_delay>
+    
<transform_performance_capturing_size_limit>100</transform_performance_capturing_size_limit>
+    <pipeline_type>Normal</pipeline_type>
+    <pipeline_status>0</pipeline_status>
+    <parameters/>
+    <name>0123-text-file-output-inherited-formats</name>
+    <name_sync_with_filename>Y</name_sync_with_filename>
+    <description>Text File Output fields with a blank Format inherit the 
conversion mask of the incoming field. d_input gets its date mask from Text 
File Input, d_select from a Select Values metadata change, and n_input a 
zero-padded Integer mask. d_override has an explicit output format that must 
win, d_default has no format anywhere and must keep the default date mask, 
s_value is a plain String, and row 2 is all nulls. 
0123-text-file-output-inherited-formats-read-back verifies the writ [...]
+    <extended_description/>
+    <created_user>-</created_user>
+    <modified_user>-</modified_user>
+    <created_date>2026/10/16 12:00:00.000</created_date>
+    <modified_date>2026/10/16 12:00:00.000</modified_date>
+  </info>
+  <transform>
+    <type>TextFileInput2</type>
+    <name>Read formatted values</name>
+    <file_type>CSV</file_type>
+    <compression>None</compression>
+    <separator>;</separator>
+    <enclosure>"</enclosure>
+    <enclosure_breaks>N</enclosure_breaks>
+    <escapechar/>
+    <header>Y</header>
+    <prependFileName>N</prependFileName>
+    <nr_headerlines>1</nr_headerlines>
+    <footer>N</footer>
+    <nr_footerlines>1</nr_footerlines>
+    <line_wrapped>N</line_wrapped>
+    <nr_wraps>1</nr_wraps>
+    <layout_paged>N</layout_paged>
+    <nr_lines_per_page>80</nr_lines_per_page>
+    <nr_lines_doc_header>0</nr_lines_doc_header>
+    <noempty>Y</noempty>
+    <include>N</include>
+    <include_field/>
+    <rownum>N</rownum>
+    <rownum_field/>
+    <rownumByFile>N</rownumByFile>
+    <format>mixed</format>
+    <encoding>UTF-8</encoding>
+    <limit>0</limit>
+    <date_format_lenient>N</date_format_lenient>
+    <date_format_locale>en_US</date_format_locale>
+    <length>Characters</length>
+    <filters/>
+    <error_count_field/>
+    <error_fields_field/>
+    <error_text_field/>
+    <error_line_skipped>N</error_line_skipped>
+    <schema_definition/>
+    <ignore_fields>N</ignore_fields>
+    <file>
+      <name>${PROJECT_HOME}/files/inherited-formats.csv</name>
+      <filemask/>
+      <exclude_filemask/>
+      <file_required>Y</file_required>
+      <include_subfolders>N</include_subfolders>
+      <type>CSV</type>
+      <compression>None</compression>
+    </file>
+    <files>
+      <file>
+        <name>${PROJECT_HOME}/files/inherited-formats.csv</name>
+        <filemask/>
+        <exclude_filemask/>
+        <file_required>Y</file_required>
+        <include_subfolders>N</include_subfolders>
+      </file>
+    </files>
+    <accept_filenames>N</accept_filenames>
+    <accept_transform_name/>
+    <passing_through_fields>N</passing_through_fields>
+    <accept_field/>
+    <add_to_result_filenames>N</add_to_result_filenames>
+    <error_ignored>N</error_ignored>
+    <file_error_field/>
+    <file_error_message_field/>
+    <skip_bad_files>N</skip_bad_files>
+    <bad_line_files_destination_directory/>
+    <bad_line_files_extension>warning</bad_line_files_extension>
+    <error_line_files_destination_directory/>
+    <error_line_files_extension>error</error_line_files_extension>
+    <line_number_files_destination_directory/>
+    <line_number_files_extension>line</line_number_files_extension>
+    <fields>
+      <field>
+        <name>id</name>
+        <position>-1</position>
+        <length>-1</length>
+        <type>Integer</type>
+        <ignore>N</ignore>
+        <format>#</format>
+        <trim_type>none</trim_type>
+        <precision>-1</precision>
+        <currency/>
+        <decimal/>
+        <group/>
+        <repeat>N</repeat>
+        <null_string/>
+        <if_null_value/>
+      </field>
+      <field>
+        <name>d_input</name>
+        <position>-1</position>
+        <length>-1</length>
+        <type>Date</type>
+        <ignore>N</ignore>
+        <format>dd/MM/yyyy</format>
+        <trim_type>none</trim_type>
+        <precision>-1</precision>
+        <currency/>
+        <decimal/>
+        <group/>
+        <repeat>N</repeat>
+        <null_string/>
+        <if_null_value/>
+      </field>
+      <field>
+        <name>d_select</name>
+        <position>-1</position>
+        <length>-1</length>
+        <type>Date</type>
+        <ignore>N</ignore>
+        <format>yyyy-MM-dd</format>
+        <trim_type>none</trim_type>
+        <precision>-1</precision>
+        <currency/>
+        <decimal/>
+        <group/>
+        <repeat>N</repeat>
+        <null_string/>
+        <if_null_value/>
+      </field>
+      <field>
+        <name>d_override</name>
+        <position>-1</position>
+        <length>-1</length>
+        <type>Date</type>
+        <ignore>N</ignore>
+        <format>dd/MM/yyyy</format>
+        <trim_type>none</trim_type>
+        <precision>-1</precision>
+        <currency/>
+        <decimal/>
+        <group/>
+        <repeat>N</repeat>
+        <null_string/>
+        <if_null_value/>
+      </field>
+      <field>
+        <name>d_default</name>
+        <position>-1</position>
+        <length>-1</length>
+        <type>Date</type>
+        <ignore>N</ignore>
+        <format/>
+        <trim_type>none</trim_type>
+        <precision>-1</precision>
+        <currency/>
+        <decimal/>
+        <group/>
+        <repeat>N</repeat>
+        <null_string/>
+        <if_null_value/>
+      </field>
+      <field>
+        <name>n_input</name>
+        <position>-1</position>
+        <length>-1</length>
+        <type>Integer</type>
+        <ignore>N</ignore>
+        <format>000000</format>
+        <trim_type>none</trim_type>
+        <precision>-1</precision>
+        <currency/>
+        <decimal/>
+        <group/>
+        <repeat>N</repeat>
+        <null_string/>
+        <if_null_value/>
+      </field>
+      <field>
+        <name>s_value</name>
+        <position>-1</position>
+        <length>-1</length>
+        <type>String</type>
+        <ignore>N</ignore>
+        <format/>
+        <trim_type>none</trim_type>
+        <precision>-1</precision>
+        <currency/>
+        <decimal/>
+        <group/>
+        <repeat>N</repeat>
+        <null_string/>
+        <if_null_value/>
+      </field>
+    </fields>
+    <distribute>Y</distribute>
+    <copies>1</copies>
+    <GUI>
+      <xloc>128</xloc>
+      <yloc>96</yloc>
+    </GUI>
+    <description/>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <attributes/>
+  </transform>
+  <transform>
+    <type>SelectValues</type>
+    <name>Set d_select mask</name>
+    <fields>
+      <select_unspecified>N</select_unspecified>
+      <meta>
+        <name>d_select</name>
+        <rename>d_select</rename>
+        <type>Date</type>
+        <length>-2</length>
+        <precision>-2</precision>
+        <storage_type/>
+        <conversion_mask>yyyyMMdd</conversion_mask>
+        <date_format_lenient>N</date_format_lenient>
+        <date_format_locale/>
+        <date_format_timezone/>
+        <lenient_string_to_number>N</lenient_string_to_number>
+        <decimal_symbol/>
+        <grouping_symbol/>
+        <currency_symbol/>
+        <encoding/>
+      </meta>
+    </fields>
+    <distribute>Y</distribute>
+    <copies>1</copies>
+    <GUI>
+      <xloc>320</xloc>
+      <yloc>96</yloc>
+    </GUI>
+    <description/>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <attributes/>
+  </transform>
+  <transform>
+    <type>TextFileOutput</type>
+    <name>Write inherited formats</name>
+    <separator>;</separator>
+    <enclosure>"</enclosure>
+    <enclosure_forced>N</enclosure_forced>
+    <enclosure_fix_disabled>N</enclosure_fix_disabled>
+    <header>Y</header>
+    <footer>N</footer>
+    <format>UNIX</format>
+    <compression>None</compression>
+    <encoding>UTF-8</encoding>
+    <endedLine/>
+    <fileNameInField>N</fileNameInField>
+    <fileNameField/>
+    <create_parent_folder>Y</create_parent_folder>
+    <file>
+      <name>${java.io.tmpdir}/0123/inherited-formats-out</name>
+      <do_not_open_new_file_init>N</do_not_open_new_file_init>
+      <extension>csv</extension>
+      <append>N</append>
+      <split>N</split>
+      <haspartno>N</haspartno>
+      <add_date>N</add_date>
+      <add_time>N</add_time>
+      <SpecifyFormat>N</SpecifyFormat>
+      <date_time_format/>
+      <add_to_result_filenames>N</add_to_result_filenames>
+      <pad>N</pad>
+      <fast_dump>N</fast_dump>
+      <splitevery>0</splitevery>
+    </file>
+    <fields>
+      <field>
+        <name>id</name>
+        <type>Integer</type>
+        <format>#</format>
+        <currency/>
+        <decimal/>
+        <group/>
+        <nullif/>
+        <trim_type>none</trim_type>
+        <length>-1</length>
+        <precision>-1</precision>
+      </field>
+      <field>
+        <name>d_input</name>
+        <type>Date</type>
+        <format/>
+        <currency/>
+        <decimal/>
+        <group/>
+        <nullif/>
+        <trim_type>none</trim_type>
+        <length>-1</length>
+        <precision>-1</precision>
+      </field>
+      <field>
+        <name>d_select</name>
+        <type>Date</type>
+        <format/>
+        <currency/>
+        <decimal/>
+        <group/>
+        <nullif/>
+        <trim_type>none</trim_type>
+        <length>-1</length>
+        <precision>-1</precision>
+      </field>
+      <field>
+        <name>d_override</name>
+        <type>Date</type>
+        <format>yyyy-MM-dd</format>
+        <currency/>
+        <decimal/>
+        <group/>
+        <nullif/>
+        <trim_type>none</trim_type>
+        <length>-1</length>
+        <precision>-1</precision>
+      </field>
+      <field>
+        <name>d_default</name>
+        <type>Date</type>
+        <format/>
+        <currency/>
+        <decimal/>
+        <group/>
+        <nullif/>
+        <trim_type>none</trim_type>
+        <length>-1</length>
+        <precision>-1</precision>
+      </field>
+      <field>
+        <name>n_input</name>
+        <type>Integer</type>
+        <format/>
+        <currency/>
+        <decimal/>
+        <group/>
+        <nullif/>
+        <trim_type>none</trim_type>
+        <length>-1</length>
+        <precision>-1</precision>
+      </field>
+      <field>
+        <name>s_value</name>
+        <type>String</type>
+        <format/>
+        <currency/>
+        <decimal/>
+        <group/>
+        <nullif/>
+        <trim_type>none</trim_type>
+        <length>-1</length>
+        <precision>-1</precision>
+      </field>
+    </fields>
+    <distribute>Y</distribute>
+    <copies>1</copies>
+    <GUI>
+      <xloc>512</xloc>
+      <yloc>96</yloc>
+    </GUI>
+    <description/>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <attributes/>
+  </transform>
+  <order>
+    <hop>
+      <from>Read formatted values</from>
+      <to>Set d_select mask</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>Set d_select mask</from>
+      <to>Write inherited formats</to>
+      <enabled>Y</enabled>
+    </hop>
+  </order>
+  <notepads/>
+  <attributes/>
+  <transform_error_handling/>
+</pipeline>
diff --git a/integration-tests/transforms/datasets/golden-inherited-formats.csv 
b/integration-tests/transforms/datasets/golden-inherited-formats.csv
new file mode 100644
index 0000000000..b81ccae9d2
--- /dev/null
+++ b/integration-tests/transforms/datasets/golden-inherited-formats.csv
@@ -0,0 +1,3 @@
+id,d_input,d_select,d_override,d_default,n_input,s_value
+1,17/05/2024,20240517,2024-05-17,2024/05/17 00:00:00.000,000042,abc
+2,,,,,,
diff --git a/integration-tests/transforms/files/inherited-formats.csv 
b/integration-tests/transforms/files/inherited-formats.csv
new file mode 100644
index 0000000000..73adb452cc
--- /dev/null
+++ b/integration-tests/transforms/files/inherited-formats.csv
@@ -0,0 +1,3 @@
+id;d_input;d_select;d_override;d_default;n_input;s_value
+1;17/05/2024;2024-05-17;17/05/2024;2024/05/17 00:00:00.000;000042;abc
+2;;;;;;
diff --git 
a/integration-tests/transforms/main-0123-text-file-output-inherited-formats.hwf 
b/integration-tests/transforms/main-0123-text-file-output-inherited-formats.hwf
new file mode 100644
index 0000000000..3bbea6ebd4
--- /dev/null
+++ 
b/integration-tests/transforms/main-0123-text-file-output-inherited-formats.hwf
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+-->
+<workflow>
+  <name>main-0123-text-file-output-inherited-formats</name>
+  <name_sync_with_filename>Y</name_sync_with_filename>
+  <description/>
+  <extended_description/>
+  <workflow_version/>
+  <created_user>-</created_user>
+  <created_date>2026/10/16 12:00:00.000</created_date>
+  <modified_user>-</modified_user>
+  <modified_date>2026/10/16 12:00:00.000</modified_date>
+  <parameters>
+    </parameters>
+  <actions>
+    <action>
+      <name>START</name>
+      <description/>
+      <type>SPECIAL</type>
+      <attributes/>
+      <repeat>N</repeat>
+      <schedulerType>0</schedulerType>
+      <intervalSeconds>0</intervalSeconds>
+      <intervalMinutes>60</intervalMinutes>
+      <hour>12</hour>
+      <minutes>0</minutes>
+      <weekDay>1</weekDay>
+      <DayOfMonth>1</DayOfMonth>
+      <parallel>N</parallel>
+      <xloc>96</xloc>
+      <yloc>80</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>0123-text-file-output-inherited-formats</name>
+      <description/>
+      <type>PIPELINE</type>
+      <attributes/>
+      
<filename>${PROJECT_HOME}/0123-text-file-output-inherited-formats.hpl</filename>
+      <params_from_previous>N</params_from_previous>
+      <exec_per_row>N</exec_per_row>
+      <clear_rows>N</clear_rows>
+      <clear_files>N</clear_files>
+      <set_logfile>N</set_logfile>
+      <logfile/>
+      <logext/>
+      <add_date>N</add_date>
+      <add_time>N</add_time>
+      <loglevel>Basic</loglevel>
+      <set_append_logfile>N</set_append_logfile>
+      <wait_until_finished>Y</wait_until_finished>
+      <follow_abort_remote>N</follow_abort_remote>
+      <create_parent_folder>N</create_parent_folder>
+      <run_configuration>local</run_configuration>
+      <parameters>
+        <pass_all_parameters>Y</pass_all_parameters>
+      </parameters>
+      <parallel>N</parallel>
+      <xloc>288</xloc>
+      <yloc>80</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>0123-text-file-output-inherited-formats-read-back UNIT</name>
+      <description/>
+      <type>RunPipelineTests</type>
+      <attributes/>
+      <test_names>
+        <test_name>
+          <name>0123-text-file-output-inherited-formats-read-back UNIT</name>
+        </test_name>
+      </test_names>
+      <parallel>N</parallel>
+      <xloc>544</xloc>
+      <yloc>80</yloc>
+      <attributes_hac/>
+    </action>
+  </actions>
+  <hops>
+    <hop>
+      <from>START</from>
+      <to>0123-text-file-output-inherited-formats</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>Y</unconditional>
+    </hop>
+    <hop>
+      <from>0123-text-file-output-inherited-formats</from>
+      <to>0123-text-file-output-inherited-formats-read-back UNIT</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+  </hops>
+  <notepads>
+  </notepads>
+  <attributes/>
+</workflow>
diff --git 
a/integration-tests/transforms/metadata/dataset/0108-data-set-output.json 
b/integration-tests/transforms/metadata/dataset/0108-data-set-output.json
index 0df7b60e90..01ec30050f 100644
--- a/integration-tests/transforms/metadata/dataset/0108-data-set-output.json
+++ b/integration-tests/transforms/metadata/dataset/0108-data-set-output.json
@@ -1,5 +1,6 @@
 {
   "base_filename": "0108-data-set-output.csv",
+  "folder_name": "${PROJECT_HOME}/output/0108-data-set-output",
   "name": "0108-data-set-output",
   "description": "",
   "dataset_fields": [
diff --git 
a/integration-tests/transforms/metadata/dataset/golden-inherited-formats.json 
b/integration-tests/transforms/metadata/dataset/golden-inherited-formats.json
new file mode 100644
index 0000000000..5f88bec321
--- /dev/null
+++ 
b/integration-tests/transforms/metadata/dataset/golden-inherited-formats.json
@@ -0,0 +1,63 @@
+{
+  "base_filename": "golden-inherited-formats.csv",
+  "name": "golden-inherited-formats",
+  "description": "Text written by Text File Output when blank output formats 
inherit the incoming field masks.",
+  "dataset_fields": [
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "id",
+      "field_format": ""
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "d_input",
+      "field_format": ""
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "d_select",
+      "field_format": ""
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "d_override",
+      "field_format": ""
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "d_default",
+      "field_format": ""
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "n_input",
+      "field_format": ""
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "s_value",
+      "field_format": ""
+    }
+  ]
+}
diff --git 
a/integration-tests/transforms/metadata/unit-test/0123-text-file-output-inherited-formats-read-back
 UNIT.json 
b/integration-tests/transforms/metadata/unit-test/0123-text-file-output-inherited-formats-read-back
 UNIT.json
new file mode 100644
index 0000000000..aab9a9e77e
--- /dev/null
+++ 
b/integration-tests/transforms/metadata/unit-test/0123-text-file-output-inherited-formats-read-back
 UNIT.json       
@@ -0,0 +1,52 @@
+{
+  "variableValues": [],
+  "database_replacements": [],
+  "autoOpening": false,
+  "basePath": "",
+  "golden_data_sets": [
+    {
+      "field_mappings": [
+        {
+          "transform_field": "id",
+          "data_set_field": "id"
+        },
+        {
+          "transform_field": "d_input",
+          "data_set_field": "d_input"
+        },
+        {
+          "transform_field": "d_select",
+          "data_set_field": "d_select"
+        },
+        {
+          "transform_field": "d_override",
+          "data_set_field": "d_override"
+        },
+        {
+          "transform_field": "d_default",
+          "data_set_field": "d_default"
+        },
+        {
+          "transform_field": "n_input",
+          "data_set_field": "n_input"
+        },
+        {
+          "transform_field": "s_value",
+          "data_set_field": "s_value"
+        }
+      ],
+      "field_order": [
+        "id"
+      ],
+      "transform_name": "OUTPUT",
+      "data_set_name": "golden-inherited-formats"
+    }
+  ],
+  "input_data_sets": [],
+  "name": "0123-text-file-output-inherited-formats-read-back UNIT",
+  "description": "Reads back the file written by 
0123-text-file-output-inherited-formats as text and compares it to the expected 
inherited formats",
+  "trans_test_tweaks": [],
+  "persist_filename": "",
+  "pipeline_filename": 
"./0123-text-file-output-inherited-formats-read-back.hpl",
+  "test_type": "UNIT_TEST"
+}
diff --git 
a/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/textfileoutput/TextFileOutputMeta.java
 
b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/textfileoutput/TextFileOutputMeta.java
index 29a855691b..876f9d1094 100644
--- 
a/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/textfileoutput/TextFileOutputMeta.java
+++ 
b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/textfileoutput/TextFileOutputMeta.java
@@ -473,7 +473,7 @@ public class TextFileOutputMeta extends 
BaseTransformMeta<TextFileOutput, TextFi
       if (v != null) {
         v.setLength(field.getLength());
         v.setPrecision(field.getPrecision());
-        if (field.getFormat() != null) {
+        if (!Utils.isEmpty(field.getFormat())) {
           v.setConversionMask(field.getFormat());
         }
         v.setDecimalSymbol(field.getDecimalSymbol());
diff --git 
a/plugins/transforms/textfile/src/test/java/org/apache/hop/pipeline/transforms/textfileoutput/TextFileOutputFormatInheritanceTest.java
 
b/plugins/transforms/textfile/src/test/java/org/apache/hop/pipeline/transforms/textfileoutput/TextFileOutputFormatInheritanceTest.java
new file mode 100644
index 0000000000..9034a63f16
--- /dev/null
+++ 
b/plugins/transforms/textfile/src/test/java/org/apache/hop/pipeline/transforms/textfileoutput/TextFileOutputFormatInheritanceTest.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.pipeline.transforms.textfileoutput;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import org.apache.hop.core.HopEnvironment;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.row.IRowMeta;
+import org.apache.hop.core.row.IValueMeta;
+import org.apache.hop.core.row.RowMeta;
+import org.apache.hop.core.row.value.ValueMetaDate;
+import org.apache.hop.core.variables.Variables;
+import org.apache.hop.core.xml.XmlHandler;
+import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
+import org.apache.hop.metadata.serializer.memory.MemoryMetadataProvider;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.w3c.dom.Node;
+
+/**
+ * An output field with a blank Format must keep the conversion mask of the 
incoming field, so a
+ * date formatted upstream is written with that format. Up to 2.17 a saved 
{@code <format/>} loaded
+ * as null; since 2.18 it loads as an empty string, which must not wipe the 
inherited mask.
+ */
+class TextFileOutputFormatInheritanceTest {
+  @RegisterExtension
+  static RestoreHopEngineEnvironmentExtension env = new 
RestoreHopEngineEnvironmentExtension();
+
+  private static final String INPUT_MASK = "dd-MM-yyyy";
+
+  @BeforeAll
+  static void setUpBeforeClass() throws HopException {
+    HopEnvironment.init();
+  }
+
+  @Test
+  void blankFormatKeepsInputDateMask() throws Exception {
+    TextFileOutputMeta meta = loadMeta(fieldXml("<format/>"));
+
+    IRowMeta row = inputRowMeta();
+    meta.getFields(row, "Text file output", null, null, new Variables(), null);
+
+    IValueMeta date = row.searchValueMeta("date");
+    assertEquals(INPUT_MASK, date.getConversionMask());
+    assertEquals("17-05-2024", date.getString(may17()));
+  }
+
+  @Test
+  void specifiedFormatOverridesInputDateMask() throws Exception {
+    TextFileOutputMeta meta = 
loadMeta(fieldXml("<format>yyyy/MM/dd</format>"));
+
+    IRowMeta row = inputRowMeta();
+    meta.getFields(row, "Text file output", null, null, new Variables(), null);
+
+    IValueMeta date = row.searchValueMeta("date");
+    assertEquals("yyyy/MM/dd", date.getConversionMask());
+    assertEquals("2024/05/17", date.getString(may17()));
+  }
+
+  private static String fieldXml(String formatTag) {
+    return "<transform><fields><field>"
+        + "<name>date</name>"
+        + "<type>Date</type>"
+        + formatTag
+        + "<currency/><decimal/><group/><nullif/>"
+        + "<trim_type>none</trim_type>"
+        + "<roundingType>half_even</roundingType>"
+        + "<length>-1</length><precision>-1</precision>"
+        + "</field></fields></transform>";
+  }
+
+  private static TextFileOutputMeta loadMeta(String xml) throws Exception {
+    Node transformNode = XmlHandler.getSubNode(XmlHandler.loadXmlString(xml), 
"transform");
+    TextFileOutputMeta meta = new TextFileOutputMeta();
+    meta.loadXml(transformNode, new MemoryMetadataProvider());
+    return meta;
+  }
+
+  private static IRowMeta inputRowMeta() {
+    IValueMeta date = new ValueMetaDate("date");
+    date.setConversionMask(INPUT_MASK);
+    IRowMeta row = new RowMeta();
+    row.addValueMeta(date);
+    return row;
+  }
+
+  private static Date may17() {
+    return new GregorianCalendar(2024, Calendar.MAY, 17).getTime();
+  }
+}

Reply via email to