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

mcasters 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 519210003f issue #1893 (XML cleanup RegEx Eval) (#6869)
519210003f is described below

commit 519210003f91d13f150f31eb2f8a94d4c81df25f
Author: Matt Casters <[email protected]>
AuthorDate: Fri Mar 27 12:59:34 2026 +0100

    issue #1893 (XML cleanup RegEx Eval) (#6869)
---
 integration-tests/transforms/0089-regex-eval.hpl   | 227 ++++++++
 .../datasets/golden-regex-eval-capture-groups.csv  |   3 +
 .../transforms/datasets/golden-regex-eval.csv      |   3 +
 .../transforms/main-0089-regex-eval.hwf            |  80 +++
 .../dataset/golden-regex-eval-capture-groups.json  |  40 ++
 .../metadata/dataset/golden-regex-eval.json        |  24 +
 .../metadata/unit-test/0089-regex-eval UNIT.json   |  61 +++
 .../pipeline/transforms/regexeval/RegexEval.java   |  51 +-
 .../transforms/regexeval/RegexEvalDialog.java      | 213 +++-----
 .../transforms/regexeval/RegexEvalMeta.java        | 594 ++++++---------------
 .../transforms/regexeval/RegexEvalMetaTest.java    | 234 ++------
 .../transforms/regexeval/RegexEvalUnitTest.java    |  12 +-
 .../RegexEval_EmptyStringVsNull_Test.java          |  19 +-
 .../regexeval/src/test/resources/regex-eval.xml    |  74 +++
 14 files changed, 856 insertions(+), 779 deletions(-)

diff --git a/integration-tests/transforms/0089-regex-eval.hpl 
b/integration-tests/transforms/0089-regex-eval.hpl
new file mode 100644
index 0000000000..41de8b5946
--- /dev/null
+++ b/integration-tests/transforms/0089-regex-eval.hpl
@@ -0,0 +1,227 @@
+<?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>
+    <name>0089-regex-eval</name>
+    <name_sync_with_filename>Y</name_sync_with_filename>
+    <description/>
+    <extended_description/>
+    <pipeline_version/>
+    <pipeline_type>Normal</pipeline_type>
+    <parameters>
+    </parameters>
+    <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>
+    <created_user>-</created_user>
+    <created_date>2026/03/25 11:18:55.162</created_date>
+    <modified_user>-</modified_user>
+    <modified_date>2026/03/25 11:18:55.162</modified_date>
+  </info>
+  <notepads>
+  </notepads>
+  <order>
+    <hop>
+      <from>strings</from>
+      <to>match hop</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>match hop</from>
+      <to>Validate</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>capture groups</from>
+      <to>Validate capture groups</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>strings</from>
+      <to>capture groups</to>
+      <enabled>Y</enabled>
+    </hop>
+  </order>
+  <transform>
+    <name>strings</name>
+    <type>DataGrid</type>
+    <description/>
+    <distribute>N</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <data>
+      <line>
+        <item>Apache Hop From The Apache Software Foundation.</item>
+      </line>
+      <line>
+        <item>Spark from the Apache Software Foundation is great.</item>
+      </line>
+    </data>
+    <fields>
+      <field>
+        <length>-1</length>
+        <precision>-1</precision>
+        <currency/>
+        <set_empty_string>N</set_empty_string>
+        <name>string</name>
+        <format/>
+        <group/>
+        <decimal/>
+        <type>String</type>
+      </field>
+    </fields>
+    <attributes/>
+    <GUI>
+      <xloc>96</xloc>
+      <yloc>64</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>match hop</name>
+    <type>RegexEval</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <allowcapturegroups>N</allowcapturegroups>
+    <canoneq>N</canoneq>
+    <caseinsensitive>Y</caseinsensitive>
+    <comment>N</comment>
+    <dotall>N</dotall>
+    <fields>
+</fields>
+    <matcher>string</matcher>
+    <multiline>N</multiline>
+    <replacefields>Y</replacefields>
+    <resultfieldname>matches</resultfieldname>
+    <script>.*hop.*</script>
+    <unicode>N</unicode>
+    <unix>N</unix>
+    <usevar>N</usevar>
+    <attributes/>
+    <GUI>
+      <xloc>208</xloc>
+      <yloc>64</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>Validate</name>
+    <type>Dummy</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <attributes/>
+    <GUI>
+      <xloc>336</xloc>
+      <yloc>64</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>capture groups</name>
+    <type>RegexEval</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <allowcapturegroups>Y</allowcapturegroups>
+    <canoneq>N</canoneq>
+    <caseinsensitive>Y</caseinsensitive>
+    <comment>N</comment>
+    <dotall>N</dotall>
+    <fields>
+      <field>
+        <currency/>
+        <decimal/>
+        <format/>
+        <group/>
+        <ifnull/>
+        <length>-1</length>
+        <name>prefix</name>
+        <nullif/>
+        <precision>-1</precision>
+        <trimtype>none</trimtype>
+        <type>String</type>
+      </field>
+      <field>
+        <currency/>
+        <decimal/>
+        <format/>
+        <group/>
+        <ifnull/>
+        <length>-1</length>
+        <name>suffix</name>
+        <nullif/>
+        <precision>-1</precision>
+        <trimtype>none</trimtype>
+        <type>String</type>
+      </field>
+    </fields>
+    <matcher>string</matcher>
+    <multiline>N</multiline>
+    <replacefields>N</replacefields>
+    <resultfieldname>matches</resultfieldname>
+    <script>(.*)Apache Software Foundation(.*)</script>
+    <unicode>N</unicode>
+    <unix>N</unix>
+    <usevar>N</usevar>
+    <attributes/>
+    <GUI>
+      <xloc>208</xloc>
+      <yloc>144</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>Validate capture groups</name>
+    <type>Dummy</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <attributes/>
+    <GUI>
+      <xloc>336</xloc>
+      <yloc>144</yloc>
+    </GUI>
+  </transform>
+  <transform_error_handling>
+  </transform_error_handling>
+  <attributes/>
+</pipeline>
diff --git 
a/integration-tests/transforms/datasets/golden-regex-eval-capture-groups.csv 
b/integration-tests/transforms/datasets/golden-regex-eval-capture-groups.csv
new file mode 100644
index 0000000000..2d238618e9
--- /dev/null
+++ b/integration-tests/transforms/datasets/golden-regex-eval-capture-groups.csv
@@ -0,0 +1,3 @@
+string,matches,prefix,suffix
+Apache Hop From The Apache Software Foundation.,Y,"Apache Hop From The ",.
+Spark from the Apache Software Foundation is great.,Y,"Spark from the "," is 
great."
diff --git a/integration-tests/transforms/datasets/golden-regex-eval.csv 
b/integration-tests/transforms/datasets/golden-regex-eval.csv
new file mode 100644
index 0000000000..10880a3c28
--- /dev/null
+++ b/integration-tests/transforms/datasets/golden-regex-eval.csv
@@ -0,0 +1,3 @@
+string,matches
+Apache Hop From The Apache Software Foundation.,Y
+Spark from the Apache Software Foundation is great.,N
diff --git a/integration-tests/transforms/main-0089-regex-eval.hwf 
b/integration-tests/transforms/main-0089-regex-eval.hwf
new file mode 100644
index 0000000000..ef6406d60e
--- /dev/null
+++ b/integration-tests/transforms/main-0089-regex-eval.hwf
@@ -0,0 +1,80 @@
+<?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-0089-regex-eval</name>
+  <name_sync_with_filename>Y</name_sync_with_filename>
+  <description/>
+  <extended_description/>
+  <workflow_version/>
+  <created_user>-</created_user>
+  <created_date>2026/03/25 11:18:35.480</created_date>
+  <modified_user>-</modified_user>
+  <modified_date>2026/03/25 11:18:35.480</modified_date>
+  <parameters>
+    </parameters>
+  <actions>
+    <action>
+      <name>Start</name>
+      <description/>
+      <type>SPECIAL</type>
+      <attributes/>
+      <DayOfMonth>1</DayOfMonth>
+      <doNotWaitOnFirstExecution>N</doNotWaitOnFirstExecution>
+      <hour>12</hour>
+      <intervalMinutes>60</intervalMinutes>
+      <intervalSeconds>0</intervalSeconds>
+      <minutes>0</minutes>
+      <repeat>N</repeat>
+      <schedulerType>0</schedulerType>
+      <weekDay>1</weekDay>
+      <parallel>N</parallel>
+      <xloc>80</xloc>
+      <yloc>48</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>0089-regex-eval UNIT</name>
+      <description/>
+      <type>RunPipelineTests</type>
+      <attributes/>
+      <test_names>
+        <test_name>
+          <name>0089-regex-eval UNIT</name>
+        </test_name>
+      </test_names>
+      <parallel>N</parallel>
+      <xloc>288</xloc>
+      <yloc>48</yloc>
+      <attributes_hac/>
+    </action>
+  </actions>
+  <hops>
+    <hop>
+      <from>Start</from>
+      <to>0089-regex-eval UNIT</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>Y</unconditional>
+    </hop>
+  </hops>
+  <notepads>
+  </notepads>
+  <attributes/>
+</workflow>
diff --git 
a/integration-tests/transforms/metadata/dataset/golden-regex-eval-capture-groups.json
 
b/integration-tests/transforms/metadata/dataset/golden-regex-eval-capture-groups.json
new file mode 100644
index 0000000000..5cdfe858ba
--- /dev/null
+++ 
b/integration-tests/transforms/metadata/dataset/golden-regex-eval-capture-groups.json
@@ -0,0 +1,40 @@
+{
+  "base_filename": "golden-regex-eval-capture-groups.csv",
+  "name": "golden-regex-eval-capture-groups",
+  "description": "",
+  "dataset_fields": [
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "string",
+      "field_format": ""
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 4,
+      "field_precision": -1,
+      "field_name": "matches",
+      "field_format": ""
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "prefix",
+      "field_format": ""
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "suffix",
+      "field_format": ""
+    }
+  ],
+  "folder_name": ""
+}
\ No newline at end of file
diff --git 
a/integration-tests/transforms/metadata/dataset/golden-regex-eval.json 
b/integration-tests/transforms/metadata/dataset/golden-regex-eval.json
new file mode 100644
index 0000000000..ab70faf1fb
--- /dev/null
+++ b/integration-tests/transforms/metadata/dataset/golden-regex-eval.json
@@ -0,0 +1,24 @@
+{
+  "base_filename": "golden-regex-eval.csv",
+  "name": "golden-regex-eval",
+  "description": "",
+  "dataset_fields": [
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_name": "string",
+      "field_format": ""
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 4,
+      "field_precision": -1,
+      "field_name": "matches",
+      "field_format": ""
+    }
+  ],
+  "folder_name": ""
+}
\ No newline at end of file
diff --git a/integration-tests/transforms/metadata/unit-test/0089-regex-eval 
UNIT.json b/integration-tests/transforms/metadata/unit-test/0089-regex-eval 
UNIT.json
new file mode 100644
index 0000000000..d80d9d8321
--- /dev/null
+++ b/integration-tests/transforms/metadata/unit-test/0089-regex-eval UNIT.json 
@@ -0,0 +1,61 @@
+{
+  "database_replacements": [],
+  "autoOpening": true,
+  "description": "",
+  "persist_filename": "",
+  "test_type": "UNIT_TEST",
+  "variableValues": [],
+  "basePath": "${HOP_UNIT_TESTS_FOLDER}",
+  "golden_data_sets": [
+    {
+      "field_mappings": [
+        {
+          "transform_field": "string",
+          "data_set_field": "string"
+        },
+        {
+          "transform_field": "matches",
+          "data_set_field": "matches"
+        }
+      ],
+      "field_order": [
+        "string",
+        "matches"
+      ],
+      "data_set_name": "golden-regex-eval",
+      "transform_name": "Validate"
+    },
+    {
+      "field_mappings": [
+        {
+          "transform_field": "string",
+          "data_set_field": "string"
+        },
+        {
+          "transform_field": "matches",
+          "data_set_field": "matches"
+        },
+        {
+          "transform_field": "prefix",
+          "data_set_field": "prefix"
+        },
+        {
+          "transform_field": "suffix",
+          "data_set_field": "suffix"
+        }
+      ],
+      "field_order": [
+        "string",
+        "matches",
+        "prefix",
+        "suffix"
+      ],
+      "data_set_name": "golden-regex-eval-capture-groups",
+      "transform_name": "Validate capture groups"
+    }
+  ],
+  "input_data_sets": [],
+  "name": "0089-regex-eval UNIT",
+  "trans_test_tweaks": [],
+  "pipeline_filename": "./0089-regex-eval.hpl"
+}
\ No newline at end of file
diff --git 
a/plugins/transforms/regexeval/src/main/java/org/apache/hop/pipeline/transforms/regexeval/RegexEval.java
 
b/plugins/transforms/regexeval/src/main/java/org/apache/hop/pipeline/transforms/regexeval/RegexEval.java
index 092cf907a3..9136305652 100644
--- 
a/plugins/transforms/regexeval/src/main/java/org/apache/hop/pipeline/transforms/regexeval/RegexEval.java
+++ 
b/plugins/transforms/regexeval/src/main/java/org/apache/hop/pipeline/transforms/regexeval/RegexEval.java
@@ -45,17 +45,13 @@ public class RegexEval extends BaseTransform<RegexEvalMeta, 
RegexEvalData> {
 
   @Override
   public boolean processRow() throws HopException {
-
     Object[] row = getRow();
-
-    if (row == null) { // no more input to be expected...
-
+    if (row == null) {
       setOutputDone();
       return false;
     }
 
     if (first) { // we just got started
-
       first = false;
 
       // get the RowMeta
@@ -65,7 +61,7 @@ public class RegexEval extends BaseTransform<RegexEvalMeta, 
RegexEvalData> {
 
       // Let's check that Result Field is given
       if (Utils.isEmpty(resolve(meta.getResultFieldName()))) {
-        if (!meta.isAllowCaptureGroupsFlagSet()) {
+        if (!meta.isAllowingCaptureGroups()) {
           // Result field is missing !
           logError(BaseMessages.getString(PKG, 
"RegexEval.Log.ErrorResultFieldMissing"));
           throw new HopTransformException(
@@ -73,7 +69,7 @@ public class RegexEval extends BaseTransform<RegexEvalMeta, 
RegexEvalData> {
         }
         data.indexOfResultField = -1;
       } else {
-        if (meta.isReplacefields()) {
+        if (meta.isReplacingFields()) {
           data.indexOfResultField = 
getInputRowMeta().indexOfValue(meta.getResultFieldName());
         }
         if (data.indexOfResultField < 0) {
@@ -105,15 +101,15 @@ public class RegexEval extends 
BaseTransform<RegexEvalMeta, RegexEvalData> {
       }
 
       // ICache the position of the CaptureGroups
-      if (meta.isAllowCaptureGroupsFlagSet()) {
-        data.positions = new int[meta.getFieldName().length];
-        String[] fieldName = meta.getFieldName();
-        for (int i = 0; i < fieldName.length; i++) {
-          if (Utils.isEmpty(fieldName[i])) {
+      if (meta.isAllowingCaptureGroups()) {
+        data.positions = new int[meta.getRegexFields().size()];
+        for (int i = 0; i < meta.getRegexFields().size(); i++) {
+          RegexEvalMeta.RegexField field = meta.getRegexFields().get(i);
+          if (Utils.isEmpty(field.getFieldName())) {
             continue;
           }
-          if (meta.isReplacefields()) {
-            data.positions[i] = data.outputRowMeta.indexOfValue(fieldName[i]);
+          if (meta.isReplacingFields()) {
+            data.positions[i] = 
data.outputRowMeta.indexOfValue(field.getFieldName());
           } else {
             data.positions[i] = captureIndex;
             captureIndex++;
@@ -147,7 +143,7 @@ public class RegexEval extends BaseTransform<RegexEvalMeta, 
RegexEvalData> {
         Matcher m = data.pattern.matcher(fieldValue);
         isMatch = m.matches();
 
-        if (meta.isAllowCaptureGroupsFlagSet() && data.positions.length != 
m.groupCount()) {
+        if (meta.isAllowingCaptureGroups() && data.positions.length != 
m.groupCount()) {
           // Runtime exception case. The number of capture groups in the
           // regex doesn't match the number of fields.
           logError(
@@ -165,6 +161,7 @@ public class RegexEval extends BaseTransform<RegexEvalMeta, 
RegexEvalData> {
         }
 
         for (int i = 0; i < data.positions.length; i++) {
+          RegexEvalMeta.RegexField field = meta.getRegexFields().get(i);
           int index = data.positions[i];
           String value;
           if (isMatch) {
@@ -189,9 +186,9 @@ public class RegexEval extends BaseTransform<RegexEvalMeta, 
RegexEvalData> {
               valueMeta.convertDataFromString(
                   value,
                   conversionValueMeta,
-                  meta.getFieldNullIf()[i],
-                  meta.getFieldIfNull()[i],
-                  meta.getFieldTrimType()[i]);
+                  field.getFieldNullIf(),
+                  field.getFieldIfNull(),
+                  field.getFieldTrimType());
 
           outputRow[index] = convertedValue;
         }
@@ -212,21 +209,13 @@ public class RegexEval extends 
BaseTransform<RegexEvalMeta, RegexEvalData> {
       //
       putRow(data.outputRowMeta, outputRow);
     } catch (HopException e) {
-      boolean sendToErrorRow = false;
-      String errorMessage = null;
-
-      if (getTransformMeta().isDoingErrorHandling()) {
-        sendToErrorRow = true;
-        errorMessage = e.toString();
-      } else {
+      if (!getTransformMeta().isDoingErrorHandling()) {
         throw new HopTransformException(
             BaseMessages.getString(PKG, "RegexEval.Log.ErrorInTransform"), e);
       }
 
-      if (sendToErrorRow) {
-        // Simply add this row to the error row
-        putError(getInputRowMeta(), outputRow, 1, errorMessage, null, 
"REGEX001");
-      }
+      // Simply add this row to the error row
+      putError(getInputRowMeta(), outputRow, 1, e.toString(), null, 
"REGEX001");
     }
     return true;
   }
@@ -240,7 +229,7 @@ public class RegexEval extends BaseTransform<RegexEvalMeta, 
RegexEvalData> {
 
       // Regular expression
       String regularexpression = meta.getScript();
-      if (meta.isUseVariableInterpolationFlagSet()) {
+      if (meta.isUsingVariables()) {
         regularexpression = resolve(meta.getScript());
       }
       if (isDetailed()) {
@@ -251,7 +240,7 @@ public class RegexEval extends BaseTransform<RegexEvalMeta, 
RegexEvalData> {
                 + regularexpression);
       }
 
-      if (meta.isCanonicalEqualityFlagSet()) {
+      if (meta.isCanonicalEqualityEnabled()) {
         data.pattern = Pattern.compile(options + regularexpression, 
Pattern.CANON_EQ);
       } else {
         data.pattern = Pattern.compile(options + regularexpression);
diff --git 
a/plugins/transforms/regexeval/src/main/java/org/apache/hop/pipeline/transforms/regexeval/RegexEvalDialog.java
 
b/plugins/transforms/regexeval/src/main/java/org/apache/hop/pipeline/transforms/regexeval/RegexEvalDialog.java
index f20127a951..d3ee7ddc2d 100644
--- 
a/plugins/transforms/regexeval/src/main/java/org/apache/hop/pipeline/transforms/regexeval/RegexEvalDialog.java
+++ 
b/plugins/transforms/regexeval/src/main/java/org/apache/hop/pipeline/transforms/regexeval/RegexEvalDialog.java
@@ -43,8 +43,6 @@ import org.eclipse.swt.custom.CCombo;
 import org.eclipse.swt.custom.CTabFolder;
 import org.eclipse.swt.custom.CTabItem;
 import org.eclipse.swt.custom.SashForm;
-import org.eclipse.swt.events.FocusEvent;
-import org.eclipse.swt.events.FocusListener;
 import org.eclipse.swt.events.ModifyListener;
 import org.eclipse.swt.events.SelectionAdapter;
 import org.eclipse.swt.events.SelectionEvent;
@@ -145,50 +143,33 @@ public class RegexEvalDialog extends BaseTransformDialog {
     wTransformSettings.setLayout(groupLayout);
 
     // fieldevaluate
-    Label wlfieldevaluate = new Label(wTransformSettings, SWT.RIGHT);
-    wlfieldevaluate.setText(BaseMessages.getString(PKG, 
"RegexEvalDialog.Matcher.Label"));
-    PropsUi.setLook(wlfieldevaluate);
-    FormData fdlfieldevaluate = new FormData();
-    fdlfieldevaluate.left = new FormAttachment(0, 0);
-    fdlfieldevaluate.top = new FormAttachment(wSpacer, margin);
-    fdlfieldevaluate.right = new FormAttachment(middle, -margin);
-    wlfieldevaluate.setLayoutData(fdlfieldevaluate);
+    Label wlFieldEvaluate = new Label(wTransformSettings, SWT.RIGHT);
+    wlFieldEvaluate.setText(BaseMessages.getString(PKG, 
"RegexEvalDialog.Matcher.Label"));
+    PropsUi.setLook(wlFieldEvaluate);
+    FormData fdlFieldEvaluate = new FormData();
+    fdlFieldEvaluate.left = new FormAttachment(0, 0);
+    fdlFieldEvaluate.top = new FormAttachment(wSpacer, margin);
+    fdlFieldEvaluate.right = new FormAttachment(middle, -margin);
+    wlFieldEvaluate.setLayoutData(fdlFieldEvaluate);
     wFieldEvaluate = new CCombo(wTransformSettings, SWT.BORDER | 
SWT.READ_ONLY);
     wFieldEvaluate.setEditable(true);
     PropsUi.setLook(wFieldEvaluate);
     wFieldEvaluate.addModifyListener(lsMod);
-    FormData fdfieldevaluate = new FormData();
-    fdfieldevaluate.left = new FormAttachment(middle, 0);
-    fdfieldevaluate.top = new FormAttachment(wSpacer, margin);
-    fdfieldevaluate.right = new FormAttachment(100, -margin);
-    wFieldEvaluate.setLayoutData(fdfieldevaluate);
+    FormData fdFieldEvaluate = new FormData();
+    fdFieldEvaluate.left = new FormAttachment(middle, 0);
+    fdFieldEvaluate.top = new FormAttachment(wSpacer, margin);
+    fdFieldEvaluate.right = new FormAttachment(100, -margin);
+    wFieldEvaluate.setLayoutData(fdFieldEvaluate);
     wFieldEvaluate.addSelectionListener(lsSel);
-    wFieldEvaluate.addFocusListener(
-        new FocusListener() {
-          @Override
-          public void focusLost(FocusEvent e) {
-            // Do nothing
-          }
-
-          @Override
-          public void focusGained(FocusEvent e) {
-            Cursor busy = new Cursor(shell.getDisplay(), SWT.CURSOR_WAIT);
-            shell.setCursor(busy);
-            getPreviousFields();
-            shell.setCursor(null);
-            busy.dispose();
-          }
-        });
-
-    // Output Fieldame
+    wFieldEvaluate.addListener(SWT.FocusIn, e -> getPreviousFields());
 
+    // Output Field name
     wResultField =
         new LabelTextVar(
             variables,
             wTransformSettings,
             BaseMessages.getString(PKG, "RegexEvalDialog.ResultField.Label"),
             BaseMessages.getString(PKG, 
"RegexEvalDialog.ResultField.Tooltip"));
-
     PropsUi.setLook(wResultField);
     wResultField.addModifyListener(lsMod);
     FormData fdResultField = new FormData();
@@ -216,14 +197,11 @@ public class RegexEvalDialog extends BaseTransformDialog {
     fdAllowCaptureGroups.top = new FormAttachment(wlAllowCaptureGroups, 0, 
SWT.CENTER);
     fdAllowCaptureGroups.right = new FormAttachment(100, 0);
     wAllowCaptureGroups.setLayoutData(fdAllowCaptureGroups);
-
-    wAllowCaptureGroups.addSelectionListener(
-        new SelectionAdapter() {
-          @Override
-          public void widgetSelected(SelectionEvent e) {
-            setFieldsEnabledStatus();
-            input.setChanged();
-          }
+    wAllowCaptureGroups.addListener(
+        SWT.Selection,
+        e -> {
+          setFieldsEnabledStatus();
+          input.setChanged();
         });
 
     // Replace fields?
@@ -244,14 +222,7 @@ public class RegexEvalDialog extends BaseTransformDialog {
     fdReplaceFields.top = new FormAttachment(wlReplaceFields, 0, SWT.CENTER);
     fdReplaceFields.right = new FormAttachment(100, 0);
     wReplaceFields.setLayoutData(fdReplaceFields);
-
-    wReplaceFields.addSelectionListener(
-        new SelectionAdapter() {
-          @Override
-          public void widgetSelected(SelectionEvent e) {
-            input.setChanged();
-          }
-        });
+    wReplaceFields.addListener(SWT.Selection, e -> input.setChanged());
 
     // settings layout
     FormData fdTransformSettings = new FormData();
@@ -338,7 +309,7 @@ public class RegexEvalDialog extends BaseTransformDialog {
     fdlFields.top = new FormAttachment(wSeparator, 0);
     wlFields.setLayoutData(fdlFields);
 
-    final int fieldsRows = input.getFieldName().length;
+    final int fieldsRows = input.getRegexFields().size();
 
     ColumnInfo[] columnInfo =
         new ColumnInfo[] {
@@ -603,7 +574,7 @@ public class RegexEvalDialog extends BaseTransformDialog {
     fdContentComp.top = new FormAttachment(0, 0);
     fdContentComp.right = new FormAttachment(100, 0);
     fdContentComp.bottom = new FormAttachment(100, 0);
-    wContentComp.setLayoutData(wContentComp);
+    wContentComp.setLayoutData(fdContentComp);
 
     wContentComp.layout();
     wContentTab.setControl(wContentComp);
@@ -649,12 +620,16 @@ public class RegexEvalDialog extends BaseTransformDialog {
   }
 
   private void getPreviousFields() {
+    Cursor busy = new Cursor(shell.getDisplay(), SWT.CURSOR_WAIT);
+
     // Save user-selected value, if applicable
     String selectedValue = wFieldEvaluate.getText();
 
     // Clear the existing list, and reload
     wFieldEvaluate.removeAll();
     try {
+      shell.setCursor(busy);
+
       IRowMeta r = pipelineMeta.getPrevTransformFields(variables, 
transformName);
       if (r != null) {
         for (String item : r.getFieldNames()) {
@@ -669,66 +644,49 @@ public class RegexEvalDialog extends BaseTransformDialog {
         wFieldEvaluate.select(0);
       }
     } catch (HopException ke) {
+      shell.setCursor(null);
       new ErrorDialog(
           shell,
           BaseMessages.getString(PKG, 
"RegexEvalDialog.FailedToGetFields.DialogTitle"),
           BaseMessages.getString(PKG, 
"RegexEvalDialog.FailedToGetFields.DialogMessage"),
           ke);
+    } finally {
+      shell.setCursor(null);
+      busy.dispose();
     }
   }
 
   /** Copy information from the meta-data input to the dialog fields. */
   public void getData() {
-    if (input.getScript() != null) {
-      wScript.setText(input.getScript());
-    }
-    if (input.getResultFieldName() != null) {
-      wResultField.setText(input.getResultFieldName());
-    }
-    if (input.getMatcher() != null) {
-      wFieldEvaluate.setText(input.getMatcher());
-    }
-
-    wUseVar.setSelection(input.isUseVariableInterpolationFlagSet());
-    wReplaceFields.setSelection(input.isReplacefields());
-    wAllowCaptureGroups.setSelection(input.isAllowCaptureGroupsFlagSet());
-    wCanonEq.setSelection(input.isCanonicalEqualityFlagSet());
-    wCaseInsensitive.setSelection(input.isCaseInsensitiveFlagSet());
-    wComment.setSelection(input.isCommentFlagSet());
-    wDotAll.setSelection(input.isDotAllFlagSet());
-    wMultiline.setSelection(input.isMultilineFlagSet());
-    wUnicode.setSelection(input.isUnicodeFlagSet());
-    wUnix.setSelection(input.isUnixLineEndingsFlagSet());
-    for (int i = 0; i < input.getFieldName().length; i++) {
+    wScript.setText(Const.NVL(input.getScript(), ""));
+    wResultField.setText(Const.NVL(input.getResultFieldName(), ""));
+    wFieldEvaluate.setText(Const.NVL(input.getMatcher(), ""));
+    wUseVar.setSelection(input.isUsingVariables());
+    wReplaceFields.setSelection(input.isReplacingFields());
+    wAllowCaptureGroups.setSelection(input.isAllowingCaptureGroups());
+    wCanonEq.setSelection(input.isCanonicalEqualityEnabled());
+    wCaseInsensitive.setSelection(input.isCaseInsensitive());
+    wComment.setSelection(input.isCommentingEnabled());
+    wDotAll.setSelection(input.isDotAllEnabled());
+    wMultiline.setSelection(input.isMultiLine());
+    wUnicode.setSelection(input.isUnicode());
+    wUnix.setSelection(input.isUnixLineEndings());
+    for (int i = 0; i < input.getRegexFields().size(); i++) {
+      RegexEvalMeta.RegexField field = input.getRegexFields().get(i);
       TableItem ti = wFields.table.getItem(i);
-      if (input.getFieldName()[i] != null) {
-        ti.setText(1, input.getFieldName()[i]);
-      }
-      ti.setText(2, 
ValueMetaFactory.getValueMetaName(input.getFieldType()[i]));
-      ti.setText(3, input.getFieldLength()[i] >= 0 ? "" + 
input.getFieldLength()[i] : "");
-      ti.setText(4, input.getFieldPrecision()[i] >= 0 ? ("" + 
input.getFieldPrecision()[i]) : "");
-      if (input.getFieldFormat()[i] != null) {
-        ti.setText(5, input.getFieldFormat()[i]);
-      }
-      if (input.getFieldGroup()[i] != null) {
-        ti.setText(6, input.getFieldGroup()[i]);
-      }
-      if (input.getFieldDecimal()[i] != null) {
-        ti.setText(7, input.getFieldDecimal()[i]);
-      }
-      if (input.getFieldCurrency()[i] != null) {
-        ti.setText(8, input.getFieldCurrency()[i]);
-      }
-      if (input.getFieldNullIf()[i] != null) {
-        ti.setText(9, input.getFieldNullIf()[i]);
-      }
-      if (input.getFieldIfNull()[i] != null) {
-        ti.setText(10, input.getFieldIfNull()[i]);
-      }
-      ti.setText(11, 
ValueMetaBase.getTrimTypeDesc(input.getFieldTrimType()[i]));
+      ti.setText(1, Const.NVL(field.getFieldName(), ""));
+      ti.setText(2, ValueMetaFactory.getValueMetaName(field.getFieldType()));
+      ti.setText(3, field.getFieldLength() >= 0 ? "" + field.getFieldLength() 
: "");
+      ti.setText(4, field.getFieldPrecision() >= 0 ? ("" + 
field.getFieldPrecision()) : "");
+      ti.setText(5, Const.NVL(field.getFieldFormat(), ""));
+      ti.setText(6, Const.NVL(field.getFieldGroup(), ""));
+      ti.setText(7, Const.NVL(field.getFieldDecimal(), ""));
+      ti.setText(8, Const.NVL(field.getFieldCurrency(), ""));
+      ti.setText(9, Const.NVL(field.getFieldNullIf(), ""));
+      ti.setText(10, Const.NVL(field.getFieldIfNull(), ""));
+      ti.setText(11, ValueMetaBase.getTrimTypeDesc(field.getFieldTrimType()));
     }
-    wFields.setRowNums();
-    wFields.optWidth(true);
+    wFields.optimizeTableView();
   }
 
   private void cancel() {
@@ -746,23 +704,22 @@ public class RegexEvalDialog extends BaseTransformDialog {
 
     setRegexOptions(input);
 
-    int nrFields = wFields.nrNonEmpty();
-
-    input.allocate(nrFields);
-
-    for (int i = 0; i < input.getFieldName().length; i++) {
-      TableItem ti = wFields.getNonEmpty(i);
-      input.getFieldName()[i] = ti.getText(1);
-      input.getFieldType()[i] = 
ValueMetaFactory.getIdForValueMeta(ti.getText(2));
-      input.getFieldLength()[i] = Const.toInt(ti.getText(3), -1);
-      input.getFieldPrecision()[i] = Const.toInt(ti.getText(4), -1);
-      input.getFieldFormat()[i] = ti.getText(5);
-      input.getFieldGroup()[i] = ti.getText(6);
-      input.getFieldDecimal()[i] = ti.getText(7);
-      input.getFieldCurrency()[i] = ti.getText(8);
-      input.getFieldNullIf()[i] = ti.getText(9);
-      input.getFieldIfNull()[i] = ti.getText(10);
-      input.getFieldTrimType()[i] = 
ValueMetaBase.getTrimTypeByDesc(ti.getText(11));
+    input.getRegexFields().clear();
+    for (TableItem item : wFields.getNonEmptyItems()) {
+      RegexEvalMeta.RegexField field = new RegexEvalMeta.RegexField();
+      input.getRegexFields().add(field);
+
+      field.setFieldName(item.getText(1));
+      field.setFieldType(ValueMetaFactory.getIdForValueMeta(item.getText(2)));
+      field.setFieldLength(Const.toInt(item.getText(3), -1));
+      field.setFieldPrecision(Const.toInt(item.getText(4), -1));
+      field.setFieldFormat(item.getText(5));
+      field.setFieldGroup(item.getText(6));
+      field.setFieldDecimal(item.getText(7));
+      field.setFieldCurrency(item.getText(8));
+      field.setFieldNullIf(item.getText(9));
+      field.setFieldIfNull(item.getText(10));
+      
field.setFieldTrimType(ValueMetaBase.getTrimTypeByDesc(item.getText(11)));
     }
 
     dispose();
@@ -779,16 +736,16 @@ public class RegexEvalDialog extends BaseTransformDialog {
     input.setScript(wScript.getText());
     input.setResultFieldName(wResultField.getText());
     input.setMatcher(wFieldEvaluate.getText());
-    input.setUseVariableInterpolationFlag(wUseVar.getSelection());
-    input.setAllowCaptureGroupsFlag(wAllowCaptureGroups.getSelection());
-    input.setReplacefields(wReplaceFields.getSelection());
-    input.setCanonicalEqualityFlag(wCanonEq.getSelection());
-    input.setCaseInsensitiveFlag(wCaseInsensitive.getSelection());
-    input.setCommentFlag(wComment.getSelection());
-    input.setDotAllFlag(wDotAll.getSelection());
-    input.setMultilineFlag(wMultiline.getSelection());
-    input.setUnicodeFlag(wUnicode.getSelection());
-    input.setUnixLineEndingsFlag(wUnix.getSelection());
+    input.setUsingVariables(wUseVar.getSelection());
+    input.setAllowingCaptureGroups(wAllowCaptureGroups.getSelection());
+    input.setReplacingFields(wReplaceFields.getSelection());
+    input.setCanonicalEqualityEnabled(wCanonEq.getSelection());
+    input.setCaseInsensitive(wCaseInsensitive.getSelection());
+    input.setCommentingEnabled(wComment.getSelection());
+    input.setDotAllEnabled(wDotAll.getSelection());
+    input.setMultiLine(wMultiline.getSelection());
+    input.setUnicode(wUnicode.getSelection());
+    input.setUnixLineEndings(wUnix.getSelection());
   }
 
   private void testRegExScript() {
@@ -800,7 +757,7 @@ public class RegexEvalDialog extends BaseTransformDialog {
             new Variables(),
             meta.getScript(),
             meta.getRegexOptions(),
-            meta.isCanonicalEqualityFlagSet());
+            meta.isCanonicalEqualityEnabled());
     wScript.setText(d.open());
   }
 }
diff --git 
a/plugins/transforms/regexeval/src/main/java/org/apache/hop/pipeline/transforms/regexeval/RegexEvalMeta.java
 
b/plugins/transforms/regexeval/src/main/java/org/apache/hop/pipeline/transforms/regexeval/RegexEvalMeta.java
index 9bff7afaff..02755eb59b 100644
--- 
a/plugins/transforms/regexeval/src/main/java/org/apache/hop/pipeline/transforms/regexeval/RegexEvalMeta.java
+++ 
b/plugins/transforms/regexeval/src/main/java/org/apache/hop/pipeline/transforms/regexeval/RegexEvalMeta.java
@@ -17,14 +17,15 @@
 
 package org.apache.hop.pipeline.transforms.regexeval;
 
+import java.util.ArrayList;
 import java.util.List;
+import lombok.Getter;
+import lombok.Setter;
 import org.apache.hop.core.CheckResult;
-import org.apache.hop.core.Const;
 import org.apache.hop.core.ICheckResult;
 import org.apache.hop.core.annotations.Transform;
 import org.apache.hop.core.exception.HopPluginException;
 import org.apache.hop.core.exception.HopTransformException;
-import org.apache.hop.core.exception.HopXmlException;
 import org.apache.hop.core.row.IRowMeta;
 import org.apache.hop.core.row.IValueMeta;
 import org.apache.hop.core.row.value.ValueMetaBase;
@@ -32,13 +33,12 @@ import org.apache.hop.core.row.value.ValueMetaBoolean;
 import org.apache.hop.core.row.value.ValueMetaFactory;
 import org.apache.hop.core.util.Utils;
 import org.apache.hop.core.variables.IVariables;
-import org.apache.hop.core.xml.XmlHandler;
 import org.apache.hop.i18n.BaseMessages;
+import org.apache.hop.metadata.api.HopMetadataProperty;
 import org.apache.hop.metadata.api.IHopMetadataProvider;
 import org.apache.hop.pipeline.PipelineMeta;
 import org.apache.hop.pipeline.transform.BaseTransformMeta;
 import org.apache.hop.pipeline.transform.TransformMeta;
-import org.w3c.dom.Node;
 
 @Transform(
     id = "RegexEval",
@@ -48,411 +48,239 @@ import org.w3c.dom.Node;
     categoryDescription = 
"i18n:org.apache.hop.pipeline.transform:BaseTransform.Category.Scripting",
     keywords = "i18n::RegexEvalMeta.keyword",
     documentationUrl = "/pipeline/transforms/regexeval.html")
+@Getter
+@Setter
 public class RegexEvalMeta extends BaseTransformMeta<RegexEval, RegexEvalData> 
{
   private static final Class<?> PKG = RegexEvalMeta.class;
   public static final String CONST_SPACES = "        ";
 
+  @Getter
+  @Setter
+  public static class RegexField {
+    @HopMetadataProperty(key = "name")
+    private String fieldName;
+
+    @HopMetadataProperty(
+        key = "type",
+        intCodeConverter = ValueMetaBase.ValueTypeCodeConverter.class)
+    private int fieldType;
+
+    @HopMetadataProperty(key = "format")
+    private String fieldFormat;
+
+    @HopMetadataProperty(key = "group")
+    private String fieldGroup;
+
+    @HopMetadataProperty(key = "decimal")
+    private String fieldDecimal;
+
+    @HopMetadataProperty(key = "currency")
+    private String fieldCurrency;
+
+    @HopMetadataProperty(key = "length")
+    private int fieldLength;
+
+    @HopMetadataProperty(key = "precision")
+    private int fieldPrecision;
+
+    @HopMetadataProperty(key = "nullif")
+    private String fieldNullIf;
+
+    @HopMetadataProperty(key = "ifnull")
+    private String fieldIfNull;
+
+    @HopMetadataProperty(
+        key = "trimtype",
+        intCodeConverter = ValueMetaBase.TrimTypeCodeConverter.class)
+    private int fieldTrimType;
+
+    public RegexField() {}
+
+    public RegexField(RegexField f) {
+      this();
+      this.fieldName = f.fieldName;
+      this.fieldType = f.fieldType;
+      this.fieldFormat = f.fieldFormat;
+      this.fieldGroup = f.fieldGroup;
+      this.fieldDecimal = f.fieldDecimal;
+      this.fieldCurrency = f.fieldCurrency;
+      this.fieldLength = f.fieldLength;
+      this.fieldPrecision = f.fieldPrecision;
+      this.fieldNullIf = f.fieldNullIf;
+      this.fieldIfNull = f.fieldIfNull;
+      this.fieldTrimType = f.fieldTrimType;
+    }
+
+    private IValueMeta constructValueMeta(IValueMeta sourceValueMeta, String 
name)
+        throws HopPluginException {
+      int type = fieldType;
+      if (type == IValueMeta.TYPE_NONE) {
+        type = IValueMeta.TYPE_STRING;
+      }
+      IValueMeta v;
+      if (sourceValueMeta == null) {
+        v = ValueMetaFactory.createValueMeta(fieldName, type);
+      } else {
+        v = ValueMetaFactory.cloneValueMeta(sourceValueMeta, type);
+      }
+      v.setLength(fieldLength);
+      v.setPrecision(fieldPrecision);
+      v.setOrigin(name);
+      v.setConversionMask(fieldFormat);
+      v.setDecimalSymbol(fieldDecimal);
+      v.setGroupingSymbol(fieldGroup);
+      v.setCurrencySymbol(fieldCurrency);
+      v.setTrimType(fieldTrimType);
+
+      return v;
+    }
+  }
+
+  @HopMetadataProperty(key = "script")
   private String script;
+
+  @HopMetadataProperty(key = "matcher")
   private String matcher;
-  private String resultfieldname;
-  private boolean usevar;
 
-  private boolean allowcapturegroups;
-  private boolean replacefields;
+  @HopMetadataProperty(key = "resultfieldname")
+  private String resultFieldName;
+
+  @HopMetadataProperty(key = "usevar")
+  private boolean usingVariables;
 
-  private boolean canoneq;
-  private boolean caseinsensitive;
-  private boolean comment;
-  private boolean dotall;
-  private boolean multiline;
+  @HopMetadataProperty(key = "allowcapturegroups")
+  private boolean allowingCaptureGroups;
+
+  @HopMetadataProperty(key = "replacefields")
+  private boolean replacingFields;
+
+  @HopMetadataProperty(key = "canoneq")
+  private boolean canonicalEqualityEnabled;
+
+  @HopMetadataProperty(key = "caseinsensitive")
+  private boolean caseInsensitive;
+
+  @HopMetadataProperty(key = "comment")
+  private boolean commentingEnabled;
+
+  @HopMetadataProperty(key = "dotall")
+  private boolean dotAllEnabled;
+
+  @HopMetadataProperty(key = "multiline")
+  private boolean multiLine;
+
+  @HopMetadataProperty(key = "unicode")
   private boolean unicode;
-  private boolean unix;
-
-  private String[] fieldName;
-  private int[] fieldType;
-  private String[] fieldFormat;
-  private String[] fieldGroup;
-  private String[] fieldDecimal;
-  private String[] fieldCurrency;
-  private int[] fieldLength;
-  private int[] fieldPrecision;
-  private String[] fieldNullIf;
-  private String[] fieldIfNull;
-  private int[] fieldTrimType;
+
+  @HopMetadataProperty(key = "unix")
+  private boolean unixLineEndings;
+
+  @HopMetadataProperty(key = "field", groupKey = "fields")
+  private List<RegexField> regexFields;
 
   public RegexEvalMeta() {
     super();
+    script = "";
+    matcher = "";
+    replacingFields = true;
+    regexFields = new ArrayList<>();
+  }
+
+  public RegexEvalMeta(RegexEvalMeta m) {
+    this();
+    this.script = m.script;
+    this.matcher = m.matcher;
+    this.resultFieldName = m.resultFieldName;
+    this.usingVariables = m.usingVariables;
+    this.allowingCaptureGroups = m.allowingCaptureGroups;
+    this.replacingFields = m.replacingFields;
+    this.canonicalEqualityEnabled = m.canonicalEqualityEnabled;
+    this.caseInsensitive = m.caseInsensitive;
+    this.commentingEnabled = m.commentingEnabled;
+    this.dotAllEnabled = m.dotAllEnabled;
+    this.multiLine = m.multiLine;
+    this.unicode = m.unicode;
+    this.unixLineEndings = m.unixLineEndings;
+    m.regexFields.forEach(field -> this.regexFields.add(new 
RegexField(field)));
   }
 
   @Override
-  public Object clone() {
-    RegexEvalMeta retval = (RegexEvalMeta) super.clone();
-
-    int nrFields = fieldName.length;
-
-    retval.allocate(nrFields);
-    System.arraycopy(fieldName, 0, retval.fieldName, 0, nrFields);
-    System.arraycopy(fieldType, 0, retval.fieldType, 0, nrFields);
-    System.arraycopy(fieldLength, 0, retval.fieldLength, 0, nrFields);
-    System.arraycopy(fieldPrecision, 0, retval.fieldPrecision, 0, nrFields);
-    System.arraycopy(fieldFormat, 0, retval.fieldFormat, 0, nrFields);
-    System.arraycopy(fieldGroup, 0, retval.fieldGroup, 0, nrFields);
-    System.arraycopy(fieldDecimal, 0, retval.fieldDecimal, 0, nrFields);
-    System.arraycopy(fieldCurrency, 0, retval.fieldCurrency, 0, nrFields);
-    System.arraycopy(fieldNullIf, 0, retval.fieldNullIf, 0, nrFields);
-    System.arraycopy(fieldIfNull, 0, retval.fieldIfNull, 0, nrFields);
-    System.arraycopy(fieldTrimType, 0, retval.fieldTrimType, 0, nrFields);
-
-    return retval;
-  }
-
-  public void allocate(int nrFields) {
-    fieldName = new String[nrFields];
-    fieldType = new int[nrFields];
-    fieldFormat = new String[nrFields];
-    fieldGroup = new String[nrFields];
-    fieldDecimal = new String[nrFields];
-    fieldCurrency = new String[nrFields];
-    fieldLength = new int[nrFields];
-    fieldPrecision = new int[nrFields];
-    fieldNullIf = new String[nrFields];
-    fieldIfNull = new String[nrFields];
-    fieldTrimType = new int[nrFields];
-  }
-
-  public String getScript() {
-    return script;
+  public RegexEvalMeta clone() {
+    return new RegexEvalMeta(this);
   }
 
   public String getRegexOptions() {
     StringBuilder options = new StringBuilder();
 
-    if (isCaseInsensitiveFlagSet()) {
+    if (isCaseInsensitive()) {
       options.append("(?i)");
     }
-    if (isCommentFlagSet()) {
+    if (isCommentingEnabled()) {
       options.append("(?x)");
     }
-    if (isDotAllFlagSet()) {
+    if (isDotAllEnabled()) {
       options.append("(?s)");
     }
-    if (isMultilineFlagSet()) {
+    if (isMultiLine()) {
       options.append("(?m)");
     }
-    if (isUnicodeFlagSet()) {
+    if (isUnicode()) {
       options.append("(?u)");
     }
-    if (isUnixLineEndingsFlagSet()) {
+    if (isUnixLineEndings()) {
       options.append("(?d)");
     }
     return options.toString();
   }
 
-  public void setScript(String script) {
-    this.script = script;
-  }
-
-  public String getMatcher() {
-    return matcher;
-  }
-
-  public void setMatcher(String matcher) {
-    this.matcher = matcher;
-  }
-
-  public String getResultFieldName() {
-    return resultfieldname;
-  }
-
-  public void setResultFieldName(String resultfieldname) {
-    this.resultfieldname = resultfieldname;
-  }
-
-  public boolean isUseVariableInterpolationFlagSet() {
-    return usevar;
-  }
-
-  public void setUseVariableInterpolationFlag(boolean usevar) {
-    this.usevar = usevar;
-  }
-
-  public boolean isAllowCaptureGroupsFlagSet() {
-    return allowcapturegroups;
-  }
-
-  public void setAllowCaptureGroupsFlag(boolean allowcapturegroups) {
-    this.allowcapturegroups = allowcapturegroups;
-  }
-
-  public boolean isReplacefields() {
-    return replacefields;
-  }
-
-  public void setReplacefields(boolean replacefields) {
-    this.replacefields = replacefields;
-  }
-
-  public boolean isCanonicalEqualityFlagSet() {
-    return canoneq;
-  }
-
-  public void setCanonicalEqualityFlag(boolean canoneq) {
-    this.canoneq = canoneq;
-  }
-
-  public boolean isCaseInsensitiveFlagSet() {
-    return caseinsensitive;
-  }
-
-  public void setCaseInsensitiveFlag(boolean caseinsensitive) {
-    this.caseinsensitive = caseinsensitive;
-  }
-
-  public boolean isCommentFlagSet() {
-    return comment;
-  }
-
-  public void setCommentFlag(boolean comment) {
-    this.comment = comment;
-  }
-
-  public boolean isDotAllFlagSet() {
-    return dotall;
-  }
-
-  public void setDotAllFlag(boolean dotall) {
-    this.dotall = dotall;
-  }
-
-  public boolean isMultilineFlagSet() {
-    return multiline;
-  }
-
-  public void setMultilineFlag(boolean multiline) {
-    this.multiline = multiline;
-  }
-
-  public boolean isUnicodeFlagSet() {
-    return unicode;
-  }
-
-  public void setUnicodeFlag(boolean unicode) {
-    this.unicode = unicode;
-  }
-
-  public boolean isUnixLineEndingsFlagSet() {
-    return unix;
-  }
-
-  public void setUnixLineEndingsFlag(boolean unix) {
-    this.unix = unix;
-  }
-
-  public String[] getFieldName() {
-    return fieldName;
-  }
-
-  public void setFieldName(String[] value) {
-    this.fieldName = value;
-  }
-
-  public int[] getFieldType() {
-    return fieldType;
-  }
-
-  public void setFieldType(int[] fieldType) {
-    this.fieldType = fieldType;
-  }
-
-  public String[] getFieldFormat() {
-    return fieldFormat;
-  }
-
-  public void setFieldFormat(String[] fieldFormat) {
-    this.fieldFormat = fieldFormat;
-  }
-
-  public String[] getFieldGroup() {
-    return fieldGroup;
-  }
-
-  public void setFieldGroup(String[] fieldGroup) {
-    this.fieldGroup = fieldGroup;
-  }
-
-  public String[] getFieldDecimal() {
-    return fieldDecimal;
-  }
-
-  public void setFieldDecimal(String[] fieldDecimal) {
-    this.fieldDecimal = fieldDecimal;
-  }
-
-  public String[] getFieldCurrency() {
-    return fieldCurrency;
-  }
-
-  public void setFieldCurrency(String[] fieldCurrency) {
-    this.fieldCurrency = fieldCurrency;
-  }
-
-  public int[] getFieldLength() {
-    return fieldLength;
-  }
-
-  public void setFieldLength(int[] fieldLength) {
-    this.fieldLength = fieldLength;
-  }
-
-  public int[] getFieldPrecision() {
-    return fieldPrecision;
-  }
-
-  public void setFieldPrecision(int[] fieldPrecision) {
-    this.fieldPrecision = fieldPrecision;
-  }
-
-  public String[] getFieldNullIf() {
-    return fieldNullIf;
-  }
-
-  public void setFieldNullIf(final String[] fieldNullIf) {
-    this.fieldNullIf = fieldNullIf;
-  }
-
-  public String[] getFieldIfNull() {
-    return fieldIfNull;
-  }
-
-  public void setFieldIfNull(final String[] fieldIfNull) {
-    this.fieldIfNull = fieldIfNull;
-  }
-
-  public int[] getFieldTrimType() {
-    return fieldTrimType;
-  }
-
-  public void setFieldTrimType(final int[] fieldTrimType) {
-    this.fieldTrimType = fieldTrimType;
-  }
-
-  private void readData(Node transformNode, IHopMetadataProvider 
metadataProvider)
-      throws HopXmlException {
-    try {
-      script = XmlHandler.getTagValue(transformNode, "script");
-      matcher = XmlHandler.getTagValue(transformNode, "matcher");
-      resultfieldname = XmlHandler.getTagValue(transformNode, 
"resultfieldname");
-      usevar = "Y".equalsIgnoreCase(XmlHandler.getTagValue(transformNode, 
"usevar"));
-      allowcapturegroups =
-          "Y".equalsIgnoreCase(XmlHandler.getTagValue(transformNode, 
"allowcapturegroups"));
-      replacefields = 
"Y".equalsIgnoreCase(XmlHandler.getTagValue(transformNode, "replacefields"));
-      canoneq = "Y".equalsIgnoreCase(XmlHandler.getTagValue(transformNode, 
"canoneq"));
-      caseinsensitive =
-          "Y".equalsIgnoreCase(XmlHandler.getTagValue(transformNode, 
"caseinsensitive"));
-      comment = "Y".equalsIgnoreCase(XmlHandler.getTagValue(transformNode, 
"comment"));
-      dotall = "Y".equalsIgnoreCase(XmlHandler.getTagValue(transformNode, 
"dotall"));
-      multiline = "Y".equalsIgnoreCase(XmlHandler.getTagValue(transformNode, 
"multiline"));
-      unicode = "Y".equalsIgnoreCase(XmlHandler.getTagValue(transformNode, 
"unicode"));
-      unix = "Y".equalsIgnoreCase(XmlHandler.getTagValue(transformNode, 
"unix"));
-
-      Node fields = XmlHandler.getSubNode(transformNode, "fields");
-      int nrFields = XmlHandler.countNodes(fields, "field");
-
-      allocate(nrFields);
-
-      for (int i = 0; i < nrFields; i++) {
-        Node fnode = XmlHandler.getSubNodeByNr(fields, "field", i);
-
-        fieldName[i] = XmlHandler.getTagValue(fnode, "name");
-        final String stype = XmlHandler.getTagValue(fnode, "type");
-        fieldFormat[i] = XmlHandler.getTagValue(fnode, "format");
-        fieldGroup[i] = XmlHandler.getTagValue(fnode, "group");
-        fieldDecimal[i] = XmlHandler.getTagValue(fnode, "decimal");
-        fieldCurrency[i] = XmlHandler.getTagValue(fnode, "currency");
-        final String slen = XmlHandler.getTagValue(fnode, "length");
-        final String sprc = XmlHandler.getTagValue(fnode, "precision");
-        fieldNullIf[i] = XmlHandler.getTagValue(fnode, "nullif");
-        fieldIfNull[i] = XmlHandler.getTagValue(fnode, "ifnull");
-        final String trim = XmlHandler.getTagValue(fnode, "trimtype");
-        fieldType[i] = ValueMetaFactory.getIdForValueMeta(stype);
-        fieldLength[i] = Const.toInt(slen, -1);
-        fieldPrecision[i] = Const.toInt(sprc, -1);
-        fieldTrimType[i] = ValueMetaBase.getTrimTypeByCode(trim);
-      }
-    } catch (Exception e) {
-      throw new HopXmlException(
-          BaseMessages.getString(PKG, 
"RegexEvalMeta.Exception.UnableToLoadTransformMetaFromXML"),
-          e);
-    }
-  }
-
-  @Override
-  public void setDefault() {
-    script = "";
-    matcher = "";
-    resultfieldname = "result";
-    usevar = false;
-    allowcapturegroups = false;
-    replacefields = true;
-    canoneq = false;
-    caseinsensitive = false;
-    comment = false;
-    dotall = false;
-    multiline = false;
-    unicode = false;
-    unix = false;
-
-    allocate(0);
-  }
-
   @Override
   public void getFields(
       IRowMeta inputRowMeta,
-      String name,
+      String transformName,
       IRowMeta[] infos,
       TransformMeta nextTransforms,
       IVariables variables,
       IHopMetadataProvider metadataProviders)
       throws HopTransformException {
     try {
-      if (!Utils.isEmpty(resultfieldname)) {
-        if (replacefields) {
-          int replaceIndex = inputRowMeta.indexOfValue(resultfieldname);
+      if (!Utils.isEmpty(resultFieldName)) {
+        if (replacingFields) {
+          int replaceIndex = inputRowMeta.indexOfValue(resultFieldName);
           if (replaceIndex < 0) {
-            IValueMeta v = new 
ValueMetaBoolean(variables.resolve(resultfieldname));
-            v.setOrigin(name);
+            IValueMeta v = new 
ValueMetaBoolean(variables.resolve(resultFieldName));
+            v.setOrigin(transformName);
             inputRowMeta.addValueMeta(v);
           } else {
             IValueMeta valueMeta = inputRowMeta.getValueMeta(replaceIndex);
             IValueMeta replaceMeta =
                 ValueMetaFactory.cloneValueMeta(valueMeta, 
IValueMeta.TYPE_BOOLEAN);
-            replaceMeta.setOrigin(name);
+            replaceMeta.setOrigin(transformName);
             inputRowMeta.setValueMeta(replaceIndex, replaceMeta);
           }
         } else {
-          IValueMeta v = new 
ValueMetaBoolean(variables.resolve(resultfieldname));
-          v.setOrigin(name);
+          IValueMeta v = new 
ValueMetaBoolean(variables.resolve(resultFieldName));
+          v.setOrigin(transformName);
           inputRowMeta.addValueMeta(v);
         }
       }
 
-      if (allowcapturegroups) {
-        for (int i = 0; i < fieldName.length; i++) {
-          if (Utils.isEmpty(fieldName[i])) {
+      if (allowingCaptureGroups) {
+        for (RegexField field : regexFields) {
+          if (Utils.isEmpty(field.fieldName)) {
             continue;
           }
 
-          if (replacefields) {
-            int replaceIndex = inputRowMeta.indexOfValue(fieldName[i]);
+          if (replacingFields) {
+            int replaceIndex = inputRowMeta.indexOfValue(field.fieldName);
             if (replaceIndex < 0) {
-              inputRowMeta.addValueMeta(constructValueMeta(null, fieldName[i], 
i, name));
+              inputRowMeta.addValueMeta(field.constructValueMeta(null, 
transformName));
             } else {
               IValueMeta valueMeta = inputRowMeta.getValueMeta(replaceIndex);
-              IValueMeta replaceMeta = constructValueMeta(valueMeta, 
fieldName[i], i, name);
+              IValueMeta replaceMeta = field.constructValueMeta(valueMeta, 
transformName);
               inputRowMeta.setValueMeta(replaceIndex, replaceMeta);
             }
           } else {
-            inputRowMeta.addValueMeta(constructValueMeta(null, fieldName[i], 
i, name));
+            inputRowMeta.addValueMeta(field.constructValueMeta(null, 
transformName));
           }
         }
       }
@@ -461,84 +289,6 @@ public class RegexEvalMeta extends 
BaseTransformMeta<RegexEval, RegexEvalData> {
     }
   }
 
-  private IValueMeta constructValueMeta(
-      IValueMeta sourceValueMeta, String fieldName, int i, String name) throws 
HopPluginException {
-    int type = fieldType[i];
-    if (type == IValueMeta.TYPE_NONE) {
-      type = IValueMeta.TYPE_STRING;
-    }
-    IValueMeta v;
-    if (sourceValueMeta == null) {
-      v = ValueMetaFactory.createValueMeta(fieldName, type);
-    } else {
-      v = ValueMetaFactory.cloneValueMeta(sourceValueMeta, type);
-    }
-    v.setLength(fieldLength[i]);
-    v.setPrecision(fieldPrecision[i]);
-    v.setOrigin(name);
-    v.setConversionMask(fieldFormat[i]);
-    v.setDecimalSymbol(fieldDecimal[i]);
-    v.setGroupingSymbol(fieldGroup[i]);
-    v.setCurrencySymbol(fieldCurrency[i]);
-    v.setTrimType(fieldTrimType[i]);
-
-    return v;
-  }
-
-  @Override
-  public void loadXml(Node transformNode, IHopMetadataProvider 
metadataProvider)
-      throws HopXmlException {
-    readData(transformNode, metadataProvider);
-  }
-
-  @Override
-  public String getXml() {
-    StringBuilder retval = new StringBuilder();
-
-    retval.append("    " + XmlHandler.addTagValue("script", script));
-    retval.append("    " + XmlHandler.addTagValue("matcher", matcher));
-    retval.append("    " + XmlHandler.addTagValue("resultfieldname", 
resultfieldname));
-    retval.append("    " + XmlHandler.addTagValue("usevar", usevar));
-    retval.append("    " + XmlHandler.addTagValue("allowcapturegroups", 
allowcapturegroups));
-    retval.append("    " + XmlHandler.addTagValue("replacefields", 
replacefields));
-    retval.append("    " + XmlHandler.addTagValue("canoneq", canoneq));
-    retval.append("    " + XmlHandler.addTagValue("caseinsensitive", 
caseinsensitive));
-    retval.append("    " + XmlHandler.addTagValue("comment", comment));
-    retval.append("    " + XmlHandler.addTagValue("dotall", dotall));
-    retval.append("    " + XmlHandler.addTagValue("multiline", multiline));
-    retval.append("    " + XmlHandler.addTagValue("unicode", unicode));
-    retval.append("    " + XmlHandler.addTagValue("unix", unix));
-
-    retval.append("    <fields>").append(Const.CR);
-    for (int i = 0; i < fieldName.length; i++) {
-      if (!Utils.isEmpty(fieldName[i])) {
-        retval.append("      <field>").append(Const.CR);
-        retval.append(CONST_SPACES).append(XmlHandler.addTagValue("name", 
fieldName[i]));
-        retval
-            .append(CONST_SPACES)
-            .append(
-                XmlHandler.addTagValue("type", 
ValueMetaFactory.getValueMetaName(fieldType[i])));
-        retval.append(CONST_SPACES).append(XmlHandler.addTagValue("format", 
fieldFormat[i]));
-        retval.append(CONST_SPACES).append(XmlHandler.addTagValue("group", 
fieldGroup[i]));
-        retval.append(CONST_SPACES).append(XmlHandler.addTagValue("decimal", 
fieldDecimal[i]));
-        retval.append(CONST_SPACES).append(XmlHandler.addTagValue("length", 
fieldLength[i]));
-        retval.append(CONST_SPACES).append(XmlHandler.addTagValue("precision", 
fieldPrecision[i]));
-        retval.append(CONST_SPACES).append(XmlHandler.addTagValue("nullif", 
fieldNullIf[i]));
-        retval.append(CONST_SPACES).append(XmlHandler.addTagValue("ifnull", 
fieldIfNull[i]));
-        retval
-            .append(CONST_SPACES)
-            .append(
-                XmlHandler.addTagValue(
-                    "trimtype", 
ValueMetaBase.getTrimTypeCode(fieldTrimType[i])));
-        retval.append(CONST_SPACES).append(XmlHandler.addTagValue("currency", 
fieldCurrency[i]));
-        retval.append("      </field>").append(Const.CR);
-      }
-    }
-    retval.append("    </fields>").append(Const.CR);
-
-    return retval.toString();
-  }
-
   @Override
   public void check(
       List<ICheckResult> remarks,
@@ -590,7 +340,7 @@ public class RegexEvalMeta extends 
BaseTransformMeta<RegexEval, RegexEvalData> {
     }
 
     // Check Result Field name
-    if (!Utils.isEmpty(resultfieldname)) {
+    if (!Utils.isEmpty(resultFieldName)) {
       cr =
           new CheckResult(
               ICheckResult.TYPE_RESULT_OK,
diff --git 
a/plugins/transforms/regexeval/src/test/java/org/apache/hop/pipeline/transforms/regexeval/RegexEvalMetaTest.java
 
b/plugins/transforms/regexeval/src/test/java/org/apache/hop/pipeline/transforms/regexeval/RegexEvalMetaTest.java
index 31b6f3e463..6fc246d763 100644
--- 
a/plugins/transforms/regexeval/src/test/java/org/apache/hop/pipeline/transforms/regexeval/RegexEvalMetaTest.java
+++ 
b/plugins/transforms/regexeval/src/test/java/org/apache/hop/pipeline/transforms/regexeval/RegexEvalMetaTest.java
@@ -18,44 +18,29 @@
 package org.apache.hop.pipeline.transforms.regexeval;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import org.apache.hop.core.HopEnvironment;
 import org.apache.hop.core.exception.HopException;
 import org.apache.hop.core.exception.HopTransformException;
-import org.apache.hop.core.plugins.PluginRegistry;
 import org.apache.hop.core.row.IRowMeta;
 import org.apache.hop.core.row.IValueMeta;
-import org.apache.hop.core.row.value.ValueMetaBase;
 import org.apache.hop.core.row.value.ValueMetaPluginType;
 import org.apache.hop.core.variables.IVariables;
 import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
-import org.apache.hop.pipeline.transform.ITransform;
-import org.apache.hop.pipeline.transforms.loadsave.LoadSaveTester;
-import org.apache.hop.pipeline.transforms.loadsave.initializer.IInitializer;
-import 
org.apache.hop.pipeline.transforms.loadsave.validator.ArrayLoadSaveValidator;
-import 
org.apache.hop.pipeline.transforms.loadsave.validator.IFieldLoadSaveValidator;
-import 
org.apache.hop.pipeline.transforms.loadsave.validator.IntLoadSaveValidator;
-import 
org.apache.hop.pipeline.transforms.loadsave.validator.PrimitiveIntArrayLoadSaveValidator;
-import 
org.apache.hop.pipeline.transforms.loadsave.validator.StringLoadSaveValidator;
+import org.apache.hop.pipeline.transform.TransformSerializationTestUtil;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
 import org.mockito.ArgumentCaptor;
 
-class RegexEvalMetaTest implements IInitializer<ITransform> {
+class RegexEvalMetaTest {
   IRowMeta mockInputRowMeta;
   IVariables mockVariableSpace;
-  LoadSaveTester loadSaveTester;
-  Class<RegexEvalMeta> testMetaClass = RegexEvalMeta.class;
 
   @RegisterExtension
   static RestoreHopEngineEnvironmentExtension env = new 
RestoreHopEngineEnvironmentExtension();
@@ -82,7 +67,7 @@ class RegexEvalMetaTest implements IInitializer<ITransform> {
     String mockName = "MOCK_NAME";
     when(mockValueMeta.getName()).thenReturn(mockName);
     when(mockInputRowMeta.getValueMeta(0)).thenReturn(mockValueMeta);
-    regexEvalMeta.setReplacefields(true);
+    regexEvalMeta.setReplacingFields(true);
     regexEvalMeta.getFields(mockInputRowMeta, name, null, null, 
mockVariableSpace, null);
     ArgumentCaptor<IValueMeta> captor = 
ArgumentCaptor.forClass(IValueMeta.class);
     verify(mockInputRowMeta).setValueMeta(eq(0), captor.capture());
@@ -100,7 +85,7 @@ class RegexEvalMetaTest implements IInitializer<ITransform> {
     String mockName = "MOCK_NAME";
     when(mockVariableSpace.resolve(resultField)).thenReturn(mockName);
     when(mockInputRowMeta.getValueMeta(0)).thenReturn(mockValueMeta);
-    regexEvalMeta.setReplacefields(true);
+    regexEvalMeta.setReplacingFields(true);
     regexEvalMeta.getFields(mockInputRowMeta, name, null, null, 
mockVariableSpace, null);
     ArgumentCaptor<IValueMeta> captor = 
ArgumentCaptor.forClass(IValueMeta.class);
     verify(mockInputRowMeta).addValueMeta(captor.capture());
@@ -108,175 +93,44 @@ class RegexEvalMetaTest implements 
IInitializer<ITransform> {
   }
 
   @Test
-  void testGetFieldsReplacesFieldIfItExists() throws HopTransformException {
-    RegexEvalMeta regexEvalMeta = new RegexEvalMeta();
-    String name = "TEST_NAME";
-    regexEvalMeta.allocate(1);
-    String fieldName = "fieldname";
-
-    regexEvalMeta.getFieldName()[0] = fieldName;
-    when(mockInputRowMeta.indexOfValue(fieldName)).thenReturn(0);
-    IValueMeta mockValueMeta = mock(IValueMeta.class);
-    String mockName = "MOCK_NAME";
-    when(mockValueMeta.getName()).thenReturn(mockName);
-    when(mockInputRowMeta.getValueMeta(0)).thenReturn(mockValueMeta);
-    regexEvalMeta.setReplacefields(true);
-    regexEvalMeta.setAllowCaptureGroupsFlag(true);
-    regexEvalMeta.getFields(mockInputRowMeta, name, null, null, 
mockVariableSpace, null);
-    ArgumentCaptor<IValueMeta> captor = 
ArgumentCaptor.forClass(IValueMeta.class);
-    verify(mockInputRowMeta).setValueMeta(eq(0), captor.capture());
-    assertEquals(mockName, captor.getValue().getName());
-  }
-
-  @Test
-  void testGetFieldsAddsFieldIfDoesntExist() throws HopTransformException {
-    RegexEvalMeta regexEvalMeta = new RegexEvalMeta();
-    String name = "TEST_NAME";
-    regexEvalMeta.allocate(1);
-    String fieldName = "fieldname";
-    regexEvalMeta.getFieldName()[0] = fieldName;
-    when(mockInputRowMeta.indexOfValue(fieldName)).thenReturn(-1);
-    IValueMeta mockValueMeta = mock(IValueMeta.class);
-    String mockName = "MOCK_NAME";
-    when(mockVariableSpace.resolve(fieldName)).thenReturn(mockName);
-    when(mockInputRowMeta.getValueMeta(0)).thenReturn(mockValueMeta);
-    regexEvalMeta.setReplacefields(true);
-    regexEvalMeta.setAllowCaptureGroupsFlag(true);
-    regexEvalMeta.getFields(mockInputRowMeta, name, null, null, 
mockVariableSpace, null);
-    ArgumentCaptor<IValueMeta> captor = 
ArgumentCaptor.forClass(IValueMeta.class);
-    verify(mockInputRowMeta).addValueMeta(captor.capture());
-    assertEquals(fieldName, captor.getValue().getName());
-  }
-
-  @BeforeEach
-  void setUpLoadSave() throws Exception {
-    HopEnvironment.init();
-    PluginRegistry.init();
-    List<String> attributes =
-        Arrays.asList(
-            "script",
-            "matcher",
-            "resultfieldname",
-            "usevar",
-            "allowcapturegroups",
-            "replacefields",
-            "canoneq",
-            "caseinsensitive",
-            "comment",
-            "dotall",
-            "multiline",
-            "unicode",
-            "unix",
-            "fieldName",
-            "fieldFormat",
-            "fieldGroup",
-            "fieldDecimal",
-            "fieldCurrency",
-            "fieldNullIf",
-            "fieldIfNull",
-            "fieldTrimType",
-            "fieldLength",
-            "fieldPrecision",
-            "fieldType");
-
-    Map<String, String> getterMap =
-        new HashMap<>() {
-          {
-            put("script", "getScript");
-            put("matcher", "getMatcher");
-            put("resultfieldname", "getResultFieldName");
-            put("usevar", "isUseVariableInterpolationFlagSet");
-            put("allowcapturegroups", "isAllowCaptureGroupsFlagSet");
-            put("replacefields", "isReplacefields");
-            put("canoneq", "isCanonicalEqualityFlagSet");
-            put("caseinsensitive", "isCaseInsensitiveFlagSet");
-            put("comment", "isCommentFlagSet");
-            put("dotall", "isDotAllFlagSet");
-            put("multiline", "isMultilineFlagSet");
-            put("unicode", "isUnicodeFlagSet");
-            put("unix", "isUnixLineEndingsFlagSet");
-            put("fieldName", "getFieldName");
-            put("fieldFormat", "getFieldFormat");
-            put("fieldGroup", "getFieldGroup");
-            put("fieldDecimal", "getFieldDecimal");
-            put("fieldCurrency", "getFieldCurrency");
-            put("fieldNullIf", "getFieldNullIf");
-            put("fieldIfNull", "getFieldIfNull");
-            put("fieldTrimType", "getFieldTrimType");
-            put("fieldLength", "getFieldLength");
-            put("fieldPrecision", "getFieldPrecision");
-            put("fieldType", "getFieldType");
-          }
-        };
-    Map<String, String> setterMap =
-        new HashMap<>() {
-          {
-            put("script", "setScript");
-            put("matcher", "setMatcher");
-            put("resultfieldname", "setResultFieldName");
-            put("usevar", "setUseVariableInterpolationFlag");
-            put("allowcapturegroups", "setAllowCaptureGroupsFlag");
-            put("replacefields", "setReplacefields");
-            put("canoneq", "setCanonicalEqualityFlag");
-            put("caseinsensitive", "setCaseInsensitiveFlag");
-            put("comment", "setCommentFlag");
-            put("dotall", "setDotAllFlag");
-            put("multiline", "setMultilineFlag");
-            put("unicode", "setUnicodeFlag");
-            put("unix", "setUnixLineEndingsFlag");
-            put("fieldName", "setFieldName");
-            put("fieldFormat", "setFieldFormat");
-            put("fieldGroup", "setFieldGroup");
-            put("fieldDecimal", "setFieldDecimal");
-            put("fieldCurrency", "setFieldCurrency");
-            put("fieldNullIf", "setFieldNullIf");
-            put("fieldIfNull", "setFieldIfNull");
-            put("fieldTrimType", "setFieldTrimType");
-            put("fieldLength", "setFieldLength");
-            put("fieldPrecision", "setFieldPrecision");
-            put("fieldType", "setFieldType");
-          }
-        };
-    IFieldLoadSaveValidator<String[]> stringArrayLoadSaveValidator =
-        new ArrayLoadSaveValidator<>(new StringLoadSaveValidator(), 5);
-
-    Map<String, IFieldLoadSaveValidator<?>> attrValidatorMap = new HashMap<>();
-    attrValidatorMap.put("fieldName", stringArrayLoadSaveValidator);
-    attrValidatorMap.put("fieldFormat", stringArrayLoadSaveValidator);
-    attrValidatorMap.put("fieldGroup", stringArrayLoadSaveValidator);
-    attrValidatorMap.put("fieldDecimal", stringArrayLoadSaveValidator);
-    attrValidatorMap.put("fieldCurrency", stringArrayLoadSaveValidator);
-    attrValidatorMap.put("fieldNullIf", stringArrayLoadSaveValidator);
-    attrValidatorMap.put("fieldIfNull", stringArrayLoadSaveValidator);
-    attrValidatorMap.put(
-        "fieldTrimType",
-        new PrimitiveIntArrayLoadSaveValidator(
-            new IntLoadSaveValidator(ValueMetaBase.getTrimTypeCodes().length), 
5));
-    attrValidatorMap.put(
-        "fieldLength", new PrimitiveIntArrayLoadSaveValidator(new 
IntLoadSaveValidator(100), 5));
-    attrValidatorMap.put(
-        "fieldPrecision", new PrimitiveIntArrayLoadSaveValidator(new 
IntLoadSaveValidator(9), 5));
-    attrValidatorMap.put(
-        "fieldType", new PrimitiveIntArrayLoadSaveValidator(new 
IntLoadSaveValidator(9), 5));
-
-    Map<String, IFieldLoadSaveValidator<?>> typeValidatorMap = new HashMap<>();
-
-    loadSaveTester =
-        new LoadSaveTester(
-            testMetaClass,
-            attributes,
-            getterMap,
-            setterMap,
-            attrValidatorMap,
-            typeValidatorMap,
-            this);
-  }
-
-  // Call the allocate method on the LoadSaveTester meta class
-  @Override
-  public void modify(ITransform someMeta) {
-    if (someMeta instanceof RegexEvalMeta) {
-      ((RegexEvalMeta) someMeta).allocate(5);
-    }
+  void testSerializationRoundTrip() throws Exception {
+    RegexEvalMeta meta =
+        TransformSerializationTestUtil.testSerialization("/regex-eval.xml", 
RegexEvalMeta.class);
+
+    assertEquals("regular-expression", meta.getScript());
+    assertEquals("regex-field", meta.getMatcher());
+    assertEquals("result", meta.getResultFieldName());
+    assertTrue(meta.isUsingVariables());
+    assertTrue(meta.isAllowingCaptureGroups());
+    assertTrue(meta.isReplacingFields());
+    assertTrue(meta.isCanonicalEqualityEnabled());
+    assertTrue(meta.isCaseInsensitive());
+    assertTrue(meta.isCommentingEnabled());
+    assertTrue(meta.isDotAllEnabled());
+    assertTrue(meta.isMultiLine());
+    assertTrue(meta.isUnicode());
+    assertTrue(meta.isUnixLineEndings());
+    assertEquals(3, meta.getRegexFields().size());
+
+    RegexEvalMeta.RegexField f = meta.getRegexFields().getFirst();
+    assertEquals("field1", f.getFieldName());
+    assertEquals(IValueMeta.TYPE_STRING, f.getFieldType());
+    assertEquals(100, f.getFieldLength());
+    assertEquals(-1, f.getFieldPrecision());
+    assertEquals(IValueMeta.TRIM_TYPE_LEFT, f.getFieldTrimType());
+
+    f = meta.getRegexFields().get(1);
+    assertEquals("field2", f.getFieldName());
+    assertEquals(IValueMeta.TYPE_INTEGER, f.getFieldType());
+    assertEquals(7, f.getFieldLength());
+    assertEquals(0, f.getFieldPrecision());
+    assertEquals(IValueMeta.TRIM_TYPE_RIGHT, f.getFieldTrimType());
+
+    f = meta.getRegexFields().getLast();
+    assertEquals("field3", f.getFieldName());
+    assertEquals(IValueMeta.TYPE_NUMBER, f.getFieldType());
+    assertEquals(9, f.getFieldLength());
+    assertEquals(2, f.getFieldPrecision());
+    assertEquals(IValueMeta.TRIM_TYPE_BOTH, f.getFieldTrimType());
   }
 }
diff --git 
a/plugins/transforms/regexeval/src/test/java/org/apache/hop/pipeline/transforms/regexeval/RegexEvalUnitTest.java
 
b/plugins/transforms/regexeval/src/test/java/org/apache/hop/pipeline/transforms/regexeval/RegexEvalUnitTest.java
index ca7ede7e27..f5ccbfcd3b 100644
--- 
a/plugins/transforms/regexeval/src/test/java/org/apache/hop/pipeline/transforms/regexeval/RegexEvalUnitTest.java
+++ 
b/plugins/transforms/regexeval/src/test/java/org/apache/hop/pipeline/transforms/regexeval/RegexEvalUnitTest.java
@@ -22,6 +22,8 @@ import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.regex.Pattern;
 import org.apache.hop.core.IRowSet;
 import org.apache.hop.core.exception.HopException;
@@ -59,9 +61,15 @@ class RegexEvalUnitTest {
             0,
             transformMockHelper.pipelineMeta,
             transformMockHelper.pipeline);
-    
when(transformMockHelper.iTransformMeta.isAllowCaptureGroupsFlagSet()).thenReturn(true);
+    
when(transformMockHelper.iTransformMeta.isAllowingCaptureGroups()).thenReturn(true);
+    List<RegexEvalMeta.RegexField> fields = new ArrayList<>();
     String[] outFields = new String[] {"a", "b", "c", "d", "e", "f", "g", "h", 
"i", "j", "k"};
-    
when(transformMockHelper.iTransformMeta.getFieldName()).thenReturn(outFields);
+    for (String outField : outFields) {
+      RegexEvalMeta.RegexField field = new RegexEvalMeta.RegexField();
+      fields.add(field);
+      field.setFieldName(outField);
+    }
+    
when(transformMockHelper.iTransformMeta.getRegexFields()).thenReturn(fields);
     when(transformMockHelper.iTransformMeta.getMatcher()).thenReturn("\\.+");
     transformMockHelper.iTransformData.pattern =
         Pattern.compile("(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)");
diff --git 
a/plugins/transforms/regexeval/src/test/java/org/apache/hop/pipeline/transforms/regexeval/RegexEval_EmptyStringVsNull_Test.java
 
b/plugins/transforms/regexeval/src/test/java/org/apache/hop/pipeline/transforms/regexeval/RegexEval_EmptyStringVsNull_Test.java
index ef201a61ac..1b95f57e3a 100644
--- 
a/plugins/transforms/regexeval/src/test/java/org/apache/hop/pipeline/transforms/regexeval/RegexEval_EmptyStringVsNull_Test.java
+++ 
b/plugins/transforms/regexeval/src/test/java/org/apache/hop/pipeline/transforms/regexeval/RegexEval_EmptyStringVsNull_Test.java
@@ -82,14 +82,21 @@ class RegexEval_EmptyStringVsNull_Test {
 
   private void executeAndAssertResults(List<Object[]> expected) throws 
Exception {
     RegexEvalMeta meta = new RegexEvalMeta();
-    meta.allocate(2);
-    meta.getFieldName()[0] = "string";
-    meta.getFieldName()[1] = "matcher";
-    meta.setFieldType(new int[] {IValueMeta.TYPE_STRING, 
IValueMeta.TYPE_STRING});
+
+    RegexEvalMeta.RegexField f1 = new RegexEvalMeta.RegexField();
+    f1.setFieldName("string");
+    f1.setFieldTrimType(IValueMeta.TYPE_STRING);
+    meta.getRegexFields().add(f1);
+
+    RegexEvalMeta.RegexField f2 = new RegexEvalMeta.RegexField();
+    f2.setFieldName("matcher");
+    f2.setFieldTrimType(IValueMeta.TYPE_STRING);
+    meta.getRegexFields().add(f2);
+
     meta.setResultFieldName("string");
-    meta.setReplacefields(true);
+    meta.setReplacingFields(true);
     meta.setMatcher("matcher");
-    meta.setAllowCaptureGroupsFlag(true);
+    meta.setAllowingCaptureGroups(true);
 
     RegexEvalData data = new RegexEvalData();
     RegexEval transform = createAndInitTransform(meta, data);
diff --git a/plugins/transforms/regexeval/src/test/resources/regex-eval.xml 
b/plugins/transforms/regexeval/src/test/resources/regex-eval.xml
new file mode 100644
index 0000000000..bbf66d1e4f
--- /dev/null
+++ b/plugins/transforms/regexeval/src/test/resources/regex-eval.xml
@@ -0,0 +1,74 @@
+<?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.
+  -->
+<transform>
+    <script>regular-expression</script>
+    <matcher>regex-field</matcher>
+    <resultfieldname>result</resultfieldname>
+    <usevar>Y</usevar>
+    <allowcapturegroups>Y</allowcapturegroups>
+    <replacefields>Y</replacefields>
+    <canoneq>Y</canoneq>
+    <caseinsensitive>Y</caseinsensitive>
+    <comment>Y</comment>
+    <dotall>Y</dotall>
+    <multiline>Y</multiline>
+    <unicode>Y</unicode>
+    <unix>Y</unix>
+    <fields>
+        <field>
+            <name>field1</name>
+            <type>String</type>
+            <format/>
+            <group/>
+            <decimal/>
+            <length>100</length>
+            <precision>-1</precision>
+            <nullif/>
+            <ifnull/>
+            <trimtype>left</trimtype>
+            <currency/>
+        </field>
+        <field>
+            <name>field2</name>
+            <type>Integer</type>
+            <format/>
+            <group/>
+            <decimal/>
+            <length>7</length>
+            <precision>0</precision>
+            <nullif/>
+            <ifnull/>
+            <trimtype>right</trimtype>
+            <currency/>
+        </field>
+        <field>
+            <name>field3</name>
+            <type>Number</type>
+            <format/>
+            <group/>
+            <decimal/>
+            <length>9</length>
+            <precision>2</precision>
+            <nullif/>
+            <ifnull/>
+            <trimtype>both</trimtype>
+            <currency/>
+        </field>
+    </fields>
+</transform>
+

Reply via email to