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 9ca1f343fb Issue #4303 : Add a Do not right-pad fields option to Text 
File Output (#8690)
9ca1f343fb is described below

commit 9ca1f343fbd97be6c5531dabfa931844e71a6dba
Author: Matt Casters <[email protected]>
AuthorDate: Fri Oct 2 06:44:43 2026 +0200

    Issue #4303 : Add a Do not right-pad fields option to Text File Output 
(#8690)
    
    * Issue #4303 : Add a Do not right-pad fields option to Text File Output
    
    * Issue #4303 : Keep padding when Right pad fields is selected
    
    * Issue #4303 : Keep historical text-file padding unless the user opts out
    
    A new transform left Do not right-pad fields checked, so it stopped padding 
short values. Saved pipelines and a new transform now keep the padding Hop 
already applies. Checking the option stops that padding, and Right pad fields 
still pads when it is selected.
---
 .../pages/pipeline/transforms/textfileoutput.adoc  |  13 ++-
 .../transforms/textfileoutput/TextFileOutput.java  |   7 +-
 .../textfileoutput/TextFileOutputDialog.java       |  48 ++++++++-
 .../textfileoutput/TextFileOutputMeta.java         |  35 ++++++-
 .../messages/messages_en_US.properties             |   4 +
 .../textfileoutput/TextFileOutputMetaTest.java     | 111 ++++++++++++++++++++-
 .../textfileoutput/TextFileOutputTest.java         |  48 +++++++++
 7 files changed, 254 insertions(+), 12 deletions(-)

diff --git 
a/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/textfileoutput.adoc
 
b/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/textfileoutput.adoc
index 45f66ce161..b645e92f45 100644
--- 
a/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/textfileoutput.adoc
+++ 
b/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/textfileoutput.adoc
@@ -28,6 +28,9 @@ The Text File Output transform is used to export data to text 
file format.
 This is commonly used to generate Comma Separated Values (CSV files) that can 
be read by spreadsheet applications.
 
 It is also possible to generate fixed width files by setting lengths on the 
fields in the fields tab.
+Short values are padded out to those lengths. Existing pipelines keep that 
output, and a new transform does too.
+Check *Do not right-pad fields* to write the value as it is. Values longer 
than the field length are still shortened.
+*Right pad fields* always pads, including when *Do not right-pad fields* is 
checked, and disables that option while it is selected.
 
 Output is buffered. Open files are flushed on an interval so slow-arriving 
rows show up without waiting for that buffer to fill. The interval is 
xref:variables.adoc[HOP_FILE_OUTPUT_MAX_STREAM_LIFE], in milliseconds. The 
default is 5 seconds. A value of 0 uses that default. Set a positive number of 
milliseconds to change the interval. A negative value, for example -1, disables 
the interval flush.
 
@@ -99,7 +102,15 @@ To use Unicode specify UTF-8 or UTF-16.
 |Compression|Allows you to compress the file and specify the type of 
compression, either .zip or .gzip, to use when compressing the output.
 
 Note: Only one file is placed in a single compressed archive.
-|Right pad fields|Add spaces to the end of the fields (or remove characters at 
the end) until they have the length specific on the Fields tab.
+|Right pad fields|Add spaces to the end of the fields until they have the 
length specified on the Fields tab.
+This always pads.
+While it is selected, Do not right-pad fields is disabled.
+|Do not right-pad fields|Do not add spaces to the end of fields to reach the 
length on the Fields tab.
+Values that are longer than that length are still shortened.
+This option is disabled while Right pad fields is selected.
+Right pad fields still pads in that case.
+Existing pipelines and new transforms leave this unchecked, so they keep 
padding.
+Check it when those trailing spaces are not wanted.
 |Fast data dump (no formatting)|Improves the performance when dumping large 
amounts of data to a text file by not including any formatting information.
 Field formats are ignored, so a Boolean is written as `true` or `false`.
 |Split every ... rows|Specify a number of rows to split the file into chunks 
of that many rows. 
diff --git 
a/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/textfileoutput/TextFileOutput.java
 
b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/textfileoutput/TextFileOutput.java
index 002f981aac..f9e616ecaf 100644
--- 
a/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/textfileoutput/TextFileOutput.java
+++ 
b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/textfileoutput/TextFileOutput.java
@@ -598,7 +598,9 @@ public class TextFileOutput extends 
BaseTransform<TextFileOutputMeta, TextFileOu
               e);
         }
       }
-      if (length > string.length()) {
+      // Saved transforms and a new transform pad, as before. Do not right-pad 
fields opts out,
+      // unless Right pad fields is selected.
+      if (length > string.length() && 
meta.getFileSettings().isPaddingFields()) {
         // we need to pad this
 
         // not all encoding use single characters, so we need to cope
@@ -674,6 +676,9 @@ public class TextFileOutput extends 
BaseTransform<TextFileOutputMeta, TextFileOu
         boolean writeEnclosures = false;
 
         if (v.isString()) {
+          // Quoting stays tied to the Right pad fields checkbox. 
isPaddingFields() is also true
+          // for saved transforms that never selected that checkbox, and using 
it here would drop
+          // enclosures from existing files.
           if (meta.isEnclosureForced() && !meta.getFileSettings().isPadded()) {
             writeEnclosures = true;
           } else if (!meta.isEnclosureFixDisabled()
diff --git 
a/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/textfileoutput/TextFileOutputDialog.java
 
b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/textfileoutput/TextFileOutputDialog.java
index 2e0a2ef428..baba50fa32 100644
--- 
a/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/textfileoutput/TextFileOutputDialog.java
+++ 
b/plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/textfileoutput/TextFileOutputDialog.java
@@ -129,6 +129,9 @@ public class TextFileOutputDialog extends 
BaseTransformDialog {
 
   private Button wPad;
 
+  private Label wlDoNotPadFields;
+  private Button wDoNotPadFields;
+
   private Button wFastDump;
 
   private Label wlSplitEvery;
@@ -867,6 +870,7 @@ public class TextFileOutputDialog extends 
BaseTransformDialog {
 
     Label wlPad = new Label(wContentComp, SWT.RIGHT);
     wlPad.setText(BaseMessages.getString(PKG, 
"TextFileOutputDialog.Pad.Label"));
+    wlPad.setToolTipText(BaseMessages.getString(PKG, 
"TextFileOutputDialog.Pad.Tooltip"));
     PropsUi.setLook(wlPad);
     FormData fdlPad = new FormData();
     fdlPad.left = new FormAttachment(0, 0);
@@ -874,6 +878,7 @@ public class TextFileOutputDialog extends 
BaseTransformDialog {
     fdlPad.right = new FormAttachment(middle, -margin);
     wlPad.setLayoutData(fdlPad);
     wPad = new Button(wContentComp, SWT.CHECK);
+    wPad.setToolTipText(BaseMessages.getString(PKG, 
"TextFileOutputDialog.Pad.Tooltip"));
     PropsUi.setLook(wPad);
     FormData fdPad = new FormData();
     fdPad.left = new FormAttachment(middle, 0);
@@ -881,6 +886,35 @@ public class TextFileOutputDialog extends 
BaseTransformDialog {
     fdPad.right = new FormAttachment(100, 0);
     wPad.setLayoutData(fdPad);
     wPad.addSelectionListener(
+        new SelectionAdapter() {
+          @Override
+          public void widgetSelected(SelectionEvent e) {
+            input.setChanged();
+            enableDoNotPadFields();
+          }
+        });
+
+    wlDoNotPadFields = new Label(wContentComp, SWT.RIGHT);
+    wlDoNotPadFields.setText(
+        BaseMessages.getString(PKG, 
"TextFileOutputDialog.DoNotPadFields.Label"));
+    wlDoNotPadFields.setToolTipText(
+        BaseMessages.getString(PKG, 
"TextFileOutputDialog.DoNotPadFields.Tooltip"));
+    PropsUi.setLook(wlDoNotPadFields);
+    FormData fdlDoNotPadFields = new FormData();
+    fdlDoNotPadFields.left = new FormAttachment(0, 0);
+    fdlDoNotPadFields.top = new FormAttachment(wPad, margin);
+    fdlDoNotPadFields.right = new FormAttachment(middle, -margin);
+    wlDoNotPadFields.setLayoutData(fdlDoNotPadFields);
+    wDoNotPadFields = new Button(wContentComp, SWT.CHECK);
+    wDoNotPadFields.setToolTipText(
+        BaseMessages.getString(PKG, 
"TextFileOutputDialog.DoNotPadFields.Tooltip"));
+    PropsUi.setLook(wDoNotPadFields);
+    FormData fdDoNotPadFields = new FormData();
+    fdDoNotPadFields.left = new FormAttachment(middle, 0);
+    fdDoNotPadFields.top = new FormAttachment(wlDoNotPadFields, 0, SWT.CENTER);
+    fdDoNotPadFields.right = new FormAttachment(100, 0);
+    wDoNotPadFields.setLayoutData(fdDoNotPadFields);
+    wDoNotPadFields.addSelectionListener(
         new SelectionAdapter() {
           @Override
           public void widgetSelected(SelectionEvent e) {
@@ -893,7 +927,7 @@ public class TextFileOutputDialog extends 
BaseTransformDialog {
     PropsUi.setLook(wlFastDump);
     FormData fdlFastDump = new FormData();
     fdlFastDump.left = new FormAttachment(0, 0);
-    fdlFastDump.top = new FormAttachment(wPad, margin);
+    fdlFastDump.top = new FormAttachment(wDoNotPadFields, margin);
     fdlFastDump.right = new FormAttachment(middle, -margin);
     wlFastDump.setLayoutData(fdlFastDump);
     wFastDump = new Button(wContentComp, SWT.CHECK);
@@ -1197,6 +1231,7 @@ public class TextFileOutputDialog extends 
BaseTransformDialog {
     getData();
 
     activeFileNameField();
+    enableDoNotPadFields();
     enableParentFolder();
     input.setChanged(changed);
     focusTransformName();
@@ -1436,6 +1471,7 @@ public class TextFileOutputDialog extends 
BaseTransformDialog {
     
wAddTransformNr.setSelection(input.getFileSettings().isTransformNrInFilename());
     wAddPartnr.setSelection(input.getFileSettings().isPartNrInFilename());
     wPad.setSelection(input.getFileSettings().isPadded());
+    wDoNotPadFields.setSelection(input.getFileSettings().isDoNotPadFields());
     wFastDump.setSelection(input.getFileSettings().isFastDump());
     wAddToResult.setSelection(input.getFileSettings().isAddToResultFiles());
 
@@ -1508,6 +1544,7 @@ public class TextFileOutputDialog extends 
BaseTransformDialog {
     tfoi.getFileSettings().setDateTimeFormat(wDateTimeFormat.getText());
     tfoi.getFileSettings().setSpecifyingFormat(wSpecifyFormat.getSelection());
     tfoi.getFileSettings().setPadded(wPad.getSelection());
+    tfoi.getFileSettings().setDoNotPadFields(wDoNotPadFields.getSelection());
     tfoi.getFileSettings().setAddToResultFiles(wAddToResult.getSelection());
     tfoi.getFileSettings().setFastDump(wFastDump.getSelection());
 
@@ -1637,4 +1674,13 @@ public class TextFileOutputDialog extends 
BaseTransformDialog {
   protected void enableParentFolder() {
     // it is enabled always in this implementation
   }
+
+  /**
+   * Right pad fields always pads, so the skip-padding option does not apply 
while it is selected.
+   */
+  private void enableDoNotPadFields() {
+    boolean rightPad = wPad.getSelection();
+    wlDoNotPadFields.setEnabled(!rightPad);
+    wDoNotPadFields.setEnabled(!rightPad);
+  }
 }
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 e90b3a9838..29a855691b 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
@@ -160,6 +160,18 @@ public class TextFileOutputMeta extends 
BaseTransformMeta<TextFileOutput, TextFi
         injectionKeyDescription = "TextFileOutput.Injection.RIGHT_PAD_FIELDS")
     private boolean padded;
 
+    /**
+     * Skip padding fields with spaces up to their length. Absent from saved 
transforms and
+     * unchecked on a new one, so both keep the historical padding. Right pad 
fields still pads when
+     * this is checked.
+     */
+    @HopMetadataProperty(
+        key = "do_not_right_pad",
+        defaultBoolean = false,
+        injectionKey = "DO_NOT_RIGHT_PAD_FIELDS",
+        injectionKeyDescription = 
"TextFileOutput.Injection.DO_NOT_RIGHT_PAD_FIELDS")
+    private boolean doNotPadFields;
+
     /** Flag: Fast dump data without field formatting */
     @HopMetadataProperty(
         key = "fast_dump",
@@ -187,6 +199,9 @@ public class TextFileOutputMeta extends 
BaseTransformMeta<TextFileOutput, TextFi
       dateInFilename = false;
       timeInFilename = false;
       padded = false;
+      // Checked only when the user opts out. A new transform and a saved one 
both leave this
+      // off, so they keep the padding Hop has always applied.
+      doNotPadFields = false;
       fastDump = false;
       addToResultFiles = true;
       fileAppended = false;
@@ -203,12 +218,25 @@ public class TextFileOutputMeta extends 
BaseTransformMeta<TextFileOutput, TextFi
       this.fileAppended = f.fileAppended;
       this.fileName = f.fileName;
       this.padded = f.padded;
+      this.doNotPadFields = f.doNotPadFields;
       this.partNrInFilename = f.partNrInFilename;
       this.specifyingFormat = f.specifyingFormat;
       this.splitEveryRows = f.splitEveryRows;
       this.timeInFilename = f.timeInFilename;
       this.transformNrInFilename = f.transformNrInFilename;
     }
+
+    /**
+     * Short values are padded out to the field length.
+     *
+     * <p>Saved transforms omit Do not right-pad fields, and a new transform 
leaves it unchecked.
+     * Both keep the padding Hop has always applied, whether or not Right pad 
fields was selected.
+     * Checking Do not right-pad fields stops that padding, unless Right pad 
fields is also
+     * selected. Right pad fields always pads.
+     */
+    public boolean isPaddingFields() {
+      return padded || !doNotPadFields;
+    }
   }
 
   /** The file compression: None, Zip or Gzip */
@@ -451,16 +479,15 @@ public class TextFileOutputMeta extends 
BaseTransformMeta<TextFileOutput, TextFi
         v.setDecimalSymbol(field.getDecimalSymbol());
         v.setGroupingSymbol(field.getGroupingSymbol());
         v.setCurrencySymbol(field.getCurrencySymbol());
-        v.setOutputPaddingEnabled(getFileSettings().isPadded());
         v.setTrimType(field.getTrimType());
         v.setRoundingType(field.getRoundingType());
         if (!Utils.isEmpty(getEncoding())) {
           v.setStringEncoding(getEncoding());
         }
 
-        // enable output padding by default to be compatible with v2.5.x
-        //
-        v.setOutputPaddingEnabled(true);
+        // v2.5 compatibility: pad unless the user checked Do not right-pad 
fields and did not
+        // select Right pad fields. Saved transforms and a new transform take 
the pad path.
+        v.setOutputPaddingEnabled(getFileSettings().isPaddingFields());
       }
     }
   }
diff --git 
a/plugins/transforms/textfile/src/main/resources/org/apache/hop/pipeline/transforms/textfileoutput/messages/messages_en_US.properties
 
b/plugins/transforms/textfile/src/main/resources/org/apache/hop/pipeline/transforms/textfileoutput/messages/messages_en_US.properties
index 0011fc949b..6c6f8641c5 100644
--- 
a/plugins/transforms/textfile/src/main/resources/org/apache/hop/pipeline/transforms/textfileoutput/messages/messages_en_US.properties
+++ 
b/plugins/transforms/textfile/src/main/resources/org/apache/hop/pipeline/transforms/textfileoutput/messages/messages_en_US.properties
@@ -58,6 +58,7 @@ TextFileOutput.Injection.OUTPUT_TRIM=Field trim type (none, 
left, both, right)
 TextFileOutput.Injection.OUTPUT_TYPE=Field type
 TextFileOutput.Injection.OUTPUT_ROUNDING=Rounding type
 TextFileOutput.Injection.RIGHT_PAD_FIELDS=Right pad fields? (Y/N)
+TextFileOutput.Injection.DO_NOT_RIGHT_PAD_FIELDS=Do not right-pad fields? (Y/N)
 TextFileOutput.Injection.RUN_AS_COMMAND=Enable this option to treat the 
filename as a command and write data into the output stream created by it.
 TextFileOutput.Injection.SEPARATOR=The separator
 TextFileOutput.Injection.SPECIFY_DATE_FORMAT=Specify date time format for 
filename? (Y/N)
@@ -120,6 +121,9 @@ TextFileOutputDialog.NameColumn.Column=Name
 TextFileOutputDialog.NoFilesFound.DialogMessage=No files found\!  Please check 
the filename/directory and options.
 TextFileOutputDialog.NullColumn.Column=Null
 TextFileOutputDialog.Pad.Label=Right pad fields
+TextFileOutputDialog.Pad.Tooltip=Add spaces to the end of fields until they 
reach the length on the Fields tab. This always pads, and disables Do not 
right-pad fields while it is selected.
+TextFileOutputDialog.DoNotPadFields.Label=Do not right-pad fields
+TextFileOutputDialog.DoNotPadFields.Tooltip=Do not add spaces to the end of 
fields to reach the length on the Fields tab. Values longer than that length 
are still shortened. This option is disabled while Right pad fields is 
selected, and Right pad fields still pads. Existing pipelines and new 
transforms leave this unchecked and keep padding. Check it to stop that padding.
 TextFileOutputDialog.PrecisionColumn.Column=Precision
 TextFileOutputDialog.SchemaDefinition.Label=Schema definition
 TextFileOutputDialog.SchemaDefinition.Tooltip=Select the schema definition you 
want to apply to incoming/outgoing rows
diff --git 
a/plugins/transforms/textfile/src/test/java/org/apache/hop/pipeline/transforms/textfileoutput/TextFileOutputMetaTest.java
 
b/plugins/transforms/textfile/src/test/java/org/apache/hop/pipeline/transforms/textfileoutput/TextFileOutputMetaTest.java
index dfc4868be2..8469f806e6 100644
--- 
a/plugins/transforms/textfile/src/test/java/org/apache/hop/pipeline/transforms/textfileoutput/TextFileOutputMetaTest.java
+++ 
b/plugins/transforms/textfile/src/test/java/org/apache/hop/pipeline/transforms/textfileoutput/TextFileOutputMetaTest.java
@@ -20,6 +20,7 @@ package org.apache.hop.pipeline.transforms.textfileoutput;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.nio.file.Files;
@@ -37,7 +38,9 @@ import org.apache.hop.core.Const;
 import org.apache.hop.core.HopEnvironment;
 import org.apache.hop.core.exception.HopException;
 import org.apache.hop.core.row.IValueMeta;
+import org.apache.hop.core.row.RowMeta;
 import org.apache.hop.core.row.value.ValueMetaFactory;
+import org.apache.hop.core.row.value.ValueMetaString;
 import org.apache.hop.core.variables.IVariables;
 import org.apache.hop.core.variables.Variables;
 import org.apache.hop.core.xml.XmlHandler;
@@ -177,16 +180,95 @@ class TextFileOutputMetaTest {
   }
 
   @Test
-  void testLoadSave() throws Exception {
-    Path path =
-        
Paths.get(Objects.requireNonNull(getClass().getResource("/text-file-output.xml")).toURI());
-    String xml = Files.readString(path);
+  void newTransformKeepsLegacyPaddingUntilTheOptionIsChecked() throws 
Exception {
     TextFileOutputMeta meta = new TextFileOutputMeta();
+    assertFalse(meta.getFileSettings().isDoNotPadFields());
+    assertFalse(meta.getFileSettings().isPadded());
+    assertTrue(meta.getFileSettings().isPaddingFields());
+
+    String xml =
+        XmlHandler.openTag(TransformMeta.XML_TAG)
+            + XmlMetadataUtil.serializeObjectToXml(meta)
+            + XmlHandler.closeTag(TransformMeta.XML_TAG);
+    assertFalse(xml.contains("<do_not_right_pad>Y</do_not_right_pad>"));
+
+    TextFileOutputMeta copy = new TextFileOutputMeta();
     XmlMetadataUtil.deSerializeFromXml(
         XmlHandler.loadXmlString(xml, TransformMeta.XML_TAG),
         TextFileOutputMeta.class,
-        meta,
+        copy,
         new MemoryMetadataProvider());
+    assertFalse(copy.getFileSettings().isDoNotPadFields());
+    assertTrue(copy.getFileSettings().isPaddingFields());
+  }
+
+  @Test
+  void savedTransformWithoutTheFlagStillEnablesOutputPadding() throws 
Exception {
+    TextFileOutputMeta meta = loadFixture();
+    assertFalse(meta.getFileSettings().isPadded());
+    assertTrue(meta.getFileSettings().isPaddingFields());
+
+    TextFileOutputMeta cloned = (TextFileOutputMeta) meta.clone();
+    assertNotSame(meta.getFileSettings(), cloned.getFileSettings());
+    assertFalse(cloned.getFileSettings().isDoNotPadFields());
+    assertTrue(cloned.getFileSettings().isPaddingFields());
+
+    RowMeta row = new RowMeta();
+    row.addValueMeta(new ValueMetaString("f1"));
+    row.addValueMeta(new ValueMetaString("f2"));
+    meta.getFields(row, "out", null, null, new Variables(), new 
MemoryMetadataProvider());
+    assertTrue(row.getValueMeta(0).isOutputPaddingEnabled());
+    assertEquals(100, row.getValueMeta(0).getLength());
+    assertTrue(row.getValueMeta(1).isOutputPaddingEnabled());
+    assertEquals(7, row.getValueMeta(1).getLength());
+  }
+
+  @Test
+  void outputPaddingFollowsDoNotRightPadFields() throws Exception {
+    TextFileOutputMeta meta = new TextFileOutputMeta();
+    TextFileField field = new TextFileField();
+    field.setName("name");
+    field.setType(IValueMeta.TYPE_STRING);
+    field.setLength(10);
+    meta.getOutputFields().add(field);
+
+    meta.getFileSettings().setDoNotPadFields(false);
+    RowMeta legacy = new RowMeta();
+    legacy.addValueMeta(new ValueMetaString("name"));
+    meta.getFields(legacy, "out", null, null, new Variables(), new 
MemoryMetadataProvider());
+    assertTrue(legacy.getValueMeta(0).isOutputPaddingEnabled());
+    assertEquals(10, legacy.getValueMeta(0).getLength());
+
+    meta.getFileSettings().setDoNotPadFields(true);
+    meta.getFileSettings().setPadded(false);
+    RowMeta noPad = new RowMeta();
+    noPad.addValueMeta(new ValueMetaString("name"));
+    meta.getFields(noPad, "out", null, null, new Variables(), new 
MemoryMetadataProvider());
+    assertFalse(noPad.getValueMeta(0).isOutputPaddingEnabled());
+  }
+
+  @Test
+  void rightPadFieldsPadsWhenDoNotRightPadIsAlsoSelected() throws Exception {
+    TextFileOutputMeta meta = new TextFileOutputMeta();
+    TextFileField field = new TextFileField();
+    field.setName("name");
+    field.setType(IValueMeta.TYPE_STRING);
+    field.setLength(10);
+    meta.getOutputFields().add(field);
+    meta.getFileSettings().setDoNotPadFields(true);
+    meta.getFileSettings().setPadded(true);
+
+    assertTrue(meta.getFileSettings().isPaddingFields());
+
+    RowMeta row = new RowMeta();
+    row.addValueMeta(new ValueMetaString("name"));
+    meta.getFields(row, "out", null, null, new Variables(), new 
MemoryMetadataProvider());
+    assertTrue(row.getValueMeta(0).isOutputPaddingEnabled());
+  }
+
+  @Test
+  void testLoadSave() throws Exception {
+    TextFileOutputMeta meta = loadFixture();
 
     validate(meta);
 
@@ -205,6 +287,22 @@ class TextFileOutputMetaTest {
     validate(metaCopy);
   }
 
+  private static TextFileOutputMeta loadFixture() throws Exception {
+    Path path =
+        Paths.get(
+            Objects.requireNonNull(
+                    
TextFileOutputMetaTest.class.getResource("/text-file-output.xml"))
+                .toURI());
+    String xml = Files.readString(path);
+    TextFileOutputMeta meta = new TextFileOutputMeta();
+    XmlMetadataUtil.deSerializeFromXml(
+        XmlHandler.loadXmlString(xml, TransformMeta.XML_TAG),
+        TextFileOutputMeta.class,
+        meta,
+        new MemoryMetadataProvider());
+    return meta;
+  }
+
   private static void validate(TextFileOutputMeta meta) {
     assertEquals("schemaDefinition", meta.getSchemaDefinition());
     assertTrue(meta.isIgnoreFields());
@@ -235,6 +333,9 @@ class TextFileOutputMetaTest {
     
assertTrue(StringUtils.isEmpty(meta.getFileSettings().getDateTimeFormat()));
     assertTrue(meta.getFileSettings().isAddToResultFiles());
     assertFalse(meta.getFileSettings().isPadded());
+    // The fixture predates the option. Right pad fields is off, and padding 
still stays on.
+    assertFalse(meta.getFileSettings().isDoNotPadFields());
+    assertTrue(meta.getFileSettings().isPaddingFields());
     assertTrue(meta.getFileSettings().isFastDump());
     assertEquals("0", meta.getFileSettings().getSplitEveryRows());
 
diff --git 
a/plugins/transforms/textfile/src/test/java/org/apache/hop/pipeline/transforms/textfileoutput/TextFileOutputTest.java
 
b/plugins/transforms/textfile/src/test/java/org/apache/hop/pipeline/transforms/textfileoutput/TextFileOutputTest.java
index cff519cc11..737232387d 100644
--- 
a/plugins/transforms/textfile/src/test/java/org/apache/hop/pipeline/transforms/textfileoutput/TextFileOutputTest.java
+++ 
b/plugins/transforms/textfile/src/test/java/org/apache/hop/pipeline/transforms/textfileoutput/TextFileOutputTest.java
@@ -829,6 +829,54 @@ class TextFileOutputTest {
     return IOUtils.toString(file.getContent().getInputStream(), 
StandardCharsets.UTF_8);
   }
 
+  @Test
+  void doNotRightPadFieldsSkipsTrailingSpaces() throws Exception {
+    assertEquals("abc       ", writeStringField("abc", 10, false, false));
+    assertEquals("abc       ", writeStringField("abc", 10, false, true));
+    assertEquals("abc", writeStringField("abc", 10, true, false));
+    // Right pad fields wins when both options are selected.
+    assertEquals("abc       ", writeStringField("abc", 10, true, true));
+    assertEquals("abcdefghij", writeStringField("abcdefghijklmnop", 10, false, 
false));
+    assertEquals("abcdefghij", writeStringField("abcdefghijklmnop", 10, true, 
true));
+  }
+
+  private String writeStringField(String value, int length, boolean doNotPad, 
boolean rightPad)
+      throws Exception {
+    TextFileOutputMeta meta = new TextFileOutputMeta();
+    meta.setEncoding(Const.UTF_8);
+    meta.setSeparator("");
+    meta.setEnclosure("");
+    meta.setEnclosureForced(false);
+    meta.setEnclosureFixDisabled(true);
+    meta.getFileSettings().setDoNotPadFields(doNotPad);
+    meta.getFileSettings().setPadded(rightPad);
+
+    TextFileOutputData data = new TextFileOutputData();
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    data.writer = baos;
+    data.binarySeparator = new byte[0];
+    data.binaryEnclosure = new byte[0];
+    data.binaryNewline = "\n".getBytes(StandardCharsets.UTF_8);
+
+    ValueMetaString valueMeta = new ValueMetaString("name");
+    valueMeta.setLength(length);
+    RowMeta rowMeta = new RowMeta();
+    rowMeta.addValueMeta(valueMeta);
+
+    TextFileOutput transform =
+        new TextFileOutput(
+            transformMockHelper.transformMeta,
+            meta,
+            data,
+            0,
+            transformMockHelper.pipelineMeta,
+            transformMockHelper.pipeline);
+    transform.writeRow(rowMeta, new Object[] {value});
+    String written = baos.toString(StandardCharsets.UTF_8);
+    assertTrue(written.endsWith("\n"), written);
+    return written.substring(0, written.length() - 1);
+  }
+
   @Test
   void testFastDumpDisableStreamEncodeTest() throws Exception {
 

Reply via email to