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

hansva 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 03e9ec1913 ext file input - Error when Escape parameter is empty, 
fixes #7244 (#7458)
03e9ec1913 is described below

commit 03e9ec191356552e4f488c0dbb1e9d71e9be5d9c
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Wed Jul 8 15:08:17 2026 +0200

    ext file input - Error when Escape parameter is empty, fixes #7244 (#7458)
---
 .../pages/pipeline/transforms/textfileinput.adoc   |   1 +
 .../transforms/0040-text-file-input-enclosure.hpl  | 460 +++++++++++++++++++++
 .../transforms/files/text-file-input-enclosure.csv |   5 +
 .../transforms/main-0040-text-file-input.hwf       |  33 ++
 .../transform/common/TextFileLineUtil.java         |   4 +-
 .../transform/common/TextFileLineUtilTest.java     |  93 +++++
 6 files changed, 594 insertions(+), 2 deletions(-)

diff --git 
a/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/textfileinput.adoc
 
b/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/textfileinput.adoc
index 9213613748..184586b210 100644
--- 
a/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/textfileinput.adoc
+++ 
b/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/textfileinput.adoc
@@ -124,6 +124,7 @@ Special characters (e.g. CHAR ASCII HEX01) can be set with 
the format $[value],
 |Escape|Specify a character or characters that are used to identify characters 
that should appear as-is in the field value when they might be interpreted as a 
control or formatting character.
 For example, if you have \ as an escape character, the text 'Not the nine 
o\'clock news' (where ' is the the enclosure character) gets parsed as Not the 
nine o'clock news instead of splitting the field value.
 Special characters (e.g. CHAR HEX01) can be set with the format $[value], e.g. 
$[01] or $[6F,FF,00,1F].
+The Escape character may be left empty, or set to the same character as the 
Enclosure. In both cases a doubled enclosure inside an enclosed field (for 
example "" when " is the enclosure) is treated as a single, literal enclosure 
character and is de-duplicated in the resulting value, following the common CSV 
convention (so "she said ""hi""" is read as she said "hi"). This also works 
when "Allow breaks in enclosed fields?" is enabled.
 |Prepend filename to headers|Enable to add the filename to each field in the 
file. This can help when you are joining data by making it clear which file 
each field comes from.
 |Header|Enable if your text file has a header row (first lines in the file). 
These typically determine the column names used in the transform. If you set 
this on a file that does not contain headers, the data values in the first row 
are interpreted as headers, which will likely produce unexpected results. In 
this case, if the field is blank, it will be named EmptyField_n, where n is the 
column's position in the row. Always check the field set to ensure the column 
names and data types hav [...]
 |Number of header lines|Specify the number of rows encompass the header in the 
file.
diff --git a/integration-tests/transforms/0040-text-file-input-enclosure.hpl 
b/integration-tests/transforms/0040-text-file-input-enclosure.hpl
new file mode 100644
index 0000000000..6bc81abd78
--- /dev/null
+++ b/integration-tests/transforms/0040-text-file-input-enclosure.hpl
@@ -0,0 +1,460 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+-->
+<pipeline>
+  <info>
+    <pipeline_version/>
+    <capture_transform_performance>N</capture_transform_performance>
+    
<transform_performance_capturing_delay>1000</transform_performance_capturing_delay>
+    
<transform_performance_capturing_size_limit>100</transform_performance_capturing_size_limit>
+    <pipeline_type>Normal</pipeline_type>
+    <pipeline_status>0</pipeline_status>
+    <parameters/>
+    <name>0040-text-file-input-enclosure</name>
+    <name_sync_with_filename>Y</name_sync_with_filename>
+    <description>Issue #7244: reading a CSV with doubled enclosures ("") and a 
line break inside an enclosure, with no Escape character set. The Escape 
character (escapechar element) is intentionally omitted so it resolves to null: 
with "break in enclosure" enabled this used to throw a NullPointerException in 
TextFileLineUtil.matchChar. Verifies the file reads without crashing, doubled 
quotes are de-duplicated and the enclosed line break keeps the record 
together.</description>
+    <extended_description/>
+    <created_user>-</created_user>
+    <modified_user>-</modified_user>
+    <created_date>2026/07/08 12:00:00.000</created_date>
+    <modified_date>2026/07/08 12:00:00.000</modified_date>
+  </info>
+  <transform>
+    <type>TextFileInput2</type>
+    <name>Read enclosed CSV</name>
+    <file_type>CSV</file_type>
+    <compression>None</compression>
+    <separator>;</separator>
+    <enclosure>"</enclosure>
+    <enclosure_breaks>Y</enclosure_breaks>
+    <escapechar/>
+    <header>Y</header>
+    <prependFileName>N</prependFileName>
+    <nr_headerlines>1</nr_headerlines>
+    <footer>N</footer>
+    <nr_footerlines>1</nr_footerlines>
+    <line_wrapped>N</line_wrapped>
+    <nr_wraps>1</nr_wraps>
+    <layout_paged>N</layout_paged>
+    <nr_lines_per_page>80</nr_lines_per_page>
+    <nr_lines_doc_header>0</nr_lines_doc_header>
+    <noempty>Y</noempty>
+    <include>N</include>
+    <include_field/>
+    <rownum>N</rownum>
+    <rownum_field/>
+    <rownumByFile>N</rownumByFile>
+    <format>mixed</format>
+    <encoding>UTF-8</encoding>
+    <limit>0</limit>
+    <date_format_lenient>Y</date_format_lenient>
+    <date_format_locale>en_US</date_format_locale>
+    <length>Characters</length>
+    <filters/>
+    <error_count_field/>
+    <error_fields_field/>
+    <error_text_field/>
+    <error_line_skipped>N</error_line_skipped>
+    <schema_definition/>
+    <ignore_fields>N</ignore_fields>
+    <shortFileFieldName/>
+    <extensionFieldName/>
+    <pathFieldName/>
+    <sizeFieldName/>
+    <hiddenFieldName/>
+    <lastModificationTimeFieldName/>
+    <uriNameFieldName/>
+    <rootUriNameFieldName/>
+    <files>
+      <file>
+        <name>${PROJECT_HOME}/files/text-file-input-enclosure.csv</name>
+        <filemask/>
+        <exclude_filemask/>
+        <file_required>N</file_required>
+        <include_subfolders>N</include_subfolders>
+      </file>
+    </files>
+    <accept_filenames>N</accept_filenames>
+    <accept_transform_name/>
+    <passing_through_fields>N</passing_through_fields>
+    <accept_field/>
+    <add_to_result_filenames>N</add_to_result_filenames>
+    <error_ignored>N</error_ignored>
+    <file_error_field/>
+    <file_error_message_field/>
+    <skip_bad_files>N</skip_bad_files>
+    <bad_line_files_destination_directory/>
+    <bad_line_files_extension>warning</bad_line_files_extension>
+    <error_line_files_destination_directory/>
+    <error_line_files_extension>error</error_line_files_extension>
+    <line_number_files_destination_directory/>
+    <line_number_files_extension>line</line_number_files_extension>
+    <fields>
+      <field>
+        <name>id</name>
+        <position>-1</position>
+        <length>-1</length>
+        <type>String</type>
+        <ignore>N</ignore>
+        <format/>
+        <trim_type>none</trim_type>
+        <precision>-1</precision>
+        <currency/>
+        <decimal/>
+        <group/>
+        <repeat>N</repeat>
+        <null_string/>
+        <if_null_value/>
+      </field>
+      <field>
+        <name>value</name>
+        <position>-1</position>
+        <length>-1</length>
+        <type>String</type>
+        <ignore>N</ignore>
+        <format/>
+        <trim_type>none</trim_type>
+        <precision>-1</precision>
+        <currency/>
+        <decimal/>
+        <group/>
+        <repeat>N</repeat>
+        <null_string/>
+        <if_null_value/>
+      </field>
+      <field>
+        <name>comment</name>
+        <position>-1</position>
+        <length>-1</length>
+        <type>String</type>
+        <ignore>N</ignore>
+        <format/>
+        <trim_type>none</trim_type>
+        <precision>-1</precision>
+        <currency/>
+        <decimal/>
+        <group/>
+        <repeat>N</repeat>
+        <null_string/>
+        <if_null_value/>
+      </field>
+    </fields>
+    <distribute>Y</distribute>
+    <copies>1</copies>
+    <GUI>
+      <xloc>128</xloc>
+      <yloc>96</yloc>
+    </GUI>
+    <description/>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <attributes/>
+  </transform>
+  <transform>
+    <type>FilterRows</type>
+    <name>Has doubled quotes?</name>
+    <compare>
+      <condition>
+        <negated>N</negated>
+        <operator>-</operator>
+        <leftvalue>comment</leftvalue>
+        <function>CONTAINS</function>
+        <rightvalue/>
+        <value>
+          <name>constant</name>
+          <type>String</type>
+          <text>""</text>
+          <length>-1</length>
+          <precision>-1</precision>
+          <isnull>N</isnull>
+          <mask/>
+        </value>
+        <conditions/>
+      </condition>
+    </compare>
+    <send_true_to>Dedup failed - Abort</send_true_to>
+    <send_false_to>Row 1 correct?</send_false_to>
+    <distribute>Y</distribute>
+    <copies>1</copies>
+    <GUI>
+      <xloc>320</xloc>
+      <yloc>96</yloc>
+    </GUI>
+    <description/>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <attributes/>
+  </transform>
+  <transform>
+    <type>Abort</type>
+    <name>Dedup failed - Abort</name>
+    <row_threshold>0</row_threshold>
+    <message>A field still contains doubled enclosures ("") - the double-quote 
de-duplication did not happen</message>
+    <always_log_rows>Y</always_log_rows>
+    <abort_option>ABORT_WITH_ERROR</abort_option>
+    <distribute>Y</distribute>
+    <copies>1</copies>
+    <GUI>
+      <xloc>320</xloc>
+      <yloc>272</yloc>
+    </GUI>
+    <description/>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <attributes/>
+  </transform>
+  <transform>
+    <type>FilterRows</type>
+    <name>Row 1 correct?</name>
+    <compare>
+      <condition>
+        <negated>N</negated>
+        <operator>-</operator>
+        <function>=</function>
+        <conditions>
+          <condition>
+            <negated>N</negated>
+            <operator>-</operator>
+            <leftvalue>id</leftvalue>
+            <function>=</function>
+            <rightvalue/>
+            <value>
+              <name>constant</name>
+              <type>String</type>
+              <text>1</text>
+              <length>-1</length>
+              <precision>-1</precision>
+              <isnull>N</isnull>
+              <mask/>
+            </value>
+            <conditions/>
+          </condition>
+          <condition>
+            <negated>N</negated>
+            <operator>AND</operator>
+            <leftvalue>comment</leftvalue>
+            <function>&lt;></function>
+            <rightvalue/>
+            <value>
+              <name>constant</name>
+              <type>String</type>
+              <text>hello this is a "nice" comment</text>
+              <length>-1</length>
+              <precision>-1</precision>
+              <isnull>N</isnull>
+              <mask/>
+            </value>
+            <conditions/>
+          </condition>
+        </conditions>
+      </condition>
+    </compare>
+    <send_true_to>Row 1 wrong - Abort</send_true_to>
+    <send_false_to>Count rows</send_false_to>
+    <distribute>Y</distribute>
+    <copies>1</copies>
+    <GUI>
+      <xloc>512</xloc>
+      <yloc>96</yloc>
+    </GUI>
+    <description/>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <attributes/>
+  </transform>
+  <transform>
+    <type>Abort</type>
+    <name>Row 1 wrong - Abort</name>
+    <row_threshold>0</row_threshold>
+    <message>Row id=1 comment is not the expected de-duplicated value 'hello 
this is a "nice" comment'</message>
+    <always_log_rows>Y</always_log_rows>
+    <abort_option>ABORT_WITH_ERROR</abort_option>
+    <distribute>Y</distribute>
+    <copies>1</copies>
+    <GUI>
+      <xloc>512</xloc>
+      <yloc>272</yloc>
+    </GUI>
+    <description/>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <attributes/>
+  </transform>
+  <transform>
+    <type>GroupBy</type>
+    <name>Count rows</name>
+    <all_rows>N</all_rows>
+    <directory>${java.io.tmpdir}</directory>
+    <prefix>grp</prefix>
+    <ignore_aggregate>N</ignore_aggregate>
+    <field_ignore/>
+    <group/>
+    <fields>
+      <field>
+        <aggregate>num_rows</aggregate>
+        <subject>id</subject>
+        <type>COUNT_ANY</type>
+        <valuefield/>
+      </field>
+    </fields>
+    <add_linenr>N</add_linenr>
+    <linenr_fieldname/>
+    <give_back_row>N</give_back_row>
+    <distribute>Y</distribute>
+    <copies>1</copies>
+    <GUI>
+      <xloc>704</xloc>
+      <yloc>96</yloc>
+    </GUI>
+    <description/>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <attributes/>
+  </transform>
+  <transform>
+    <type>FilterRows</type>
+    <name>Check row count</name>
+    <compare>
+      <condition>
+        <negated>N</negated>
+        <operator>-</operator>
+        <leftvalue>num_rows</leftvalue>
+        <function>=</function>
+        <rightvalue/>
+        <value>
+          <name>constant</name>
+          <type>Integer</type>
+          <text>3</text>
+          <length>-1</length>
+          <precision>0</precision>
+          <isnull>N</isnull>
+          <mask>####0;-####0</mask>
+        </value>
+        <conditions/>
+      </condition>
+    </compare>
+    <send_true_to>Success</send_true_to>
+    <send_false_to>Wrong row count - Abort</send_false_to>
+    <distribute>Y</distribute>
+    <copies>1</copies>
+    <GUI>
+      <xloc>896</xloc>
+      <yloc>96</yloc>
+    </GUI>
+    <description/>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <attributes/>
+  </transform>
+  <transform>
+    <type>Abort</type>
+    <name>Wrong row count - Abort</name>
+    <row_threshold>0</row_threshold>
+    <message>Unexpected row count (expected 3 data rows - a line break inside 
an enclosure must not split a record)</message>
+    <always_log_rows>Y</always_log_rows>
+    <abort_option>ABORT_WITH_ERROR</abort_option>
+    <distribute>Y</distribute>
+    <copies>1</copies>
+    <GUI>
+      <xloc>896</xloc>
+      <yloc>272</yloc>
+    </GUI>
+    <description/>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <attributes/>
+  </transform>
+  <transform>
+    <type>Dummy</type>
+    <name>Success</name>
+    <distribute>Y</distribute>
+    <copies>1</copies>
+    <GUI>
+      <xloc>1088</xloc>
+      <yloc>96</yloc>
+    </GUI>
+    <description/>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <attributes/>
+  </transform>
+  <order>
+    <hop>
+      <from>Read enclosed CSV</from>
+      <to>Has doubled quotes?</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>Has doubled quotes?</from>
+      <to>Dedup failed - Abort</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>Has doubled quotes?</from>
+      <to>Row 1 correct?</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>Row 1 correct?</from>
+      <to>Row 1 wrong - Abort</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>Row 1 correct?</from>
+      <to>Count rows</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>Count rows</from>
+      <to>Check row count</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>Check row count</from>
+      <to>Success</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>Check row count</from>
+      <to>Wrong row count - Abort</to>
+      <enabled>Y</enabled>
+    </hop>
+  </order>
+  <notepads/>
+  <attributes/>
+  <transform_error_handling/>
+</pipeline>
diff --git a/integration-tests/transforms/files/text-file-input-enclosure.csv 
b/integration-tests/transforms/files/text-file-input-enclosure.csv
new file mode 100644
index 0000000000..9b5114db6a
--- /dev/null
+++ b/integration-tests/transforms/files/text-file-input-enclosure.csv
@@ -0,0 +1,5 @@
+"id";"value";"comment"
+"1";"val01";"hello this is a ""nice"" comment"
+"2";"val02";"line one
+line two"
+"3";"val03";"plain comment"
diff --git a/integration-tests/transforms/main-0040-text-file-input.hwf 
b/integration-tests/transforms/main-0040-text-file-input.hwf
index a2491549b5..6214b15545 100644
--- a/integration-tests/transforms/main-0040-text-file-input.hwf
+++ b/integration-tests/transforms/main-0040-text-file-input.hwf
@@ -89,6 +89,32 @@ limitations under the License.
       <parallel>N</parallel>
       <attributes_hac/>
     </action>
+    <action>
+      <filename>${PROJECT_HOME}/0040-text-file-input-enclosure.hpl</filename>
+      <params_from_previous>N</params_from_previous>
+      <exec_per_row>N</exec_per_row>
+      <clear_rows>N</clear_rows>
+      <clear_files>N</clear_files>
+      <create_parent_folder>N</create_parent_folder>
+      <set_logfile>N</set_logfile>
+      <set_append_logfile>N</set_append_logfile>
+      <add_date>N</add_date>
+      <add_time>N</add_time>
+      <loglevel>Basic</loglevel>
+      <wait_until_finished>Y</wait_until_finished>
+      <parameters>
+        <pass_all_parameters>Y</pass_all_parameters>
+      </parameters>
+      <run_configuration>local</run_configuration>
+      <name>0040-text-file-input-enclosure.hpl</name>
+      <description/>
+      <type>PIPELINE</type>
+      <attributes/>
+      <xloc>944</xloc>
+      <yloc>112</yloc>
+      <parallel>N</parallel>
+      <attributes_hac/>
+    </action>
   </actions>
   <hops>
     <hop>
@@ -105,6 +131,13 @@ limitations under the License.
       <unconditional>N</unconditional>
       <enabled>Y</enabled>
     </hop>
+    <hop>
+      <from>0040-text-file-input-rownum.hpl</from>
+      <to>0040-text-file-input-enclosure.hpl</to>
+      <evaluation>Y</evaluation>
+      <unconditional>N</unconditional>
+      <enabled>Y</enabled>
+    </hop>
   </hops>
   <notepads>
     <notepad>
diff --git 
a/ui/src/main/java/org/apache/hop/ui/pipeline/transform/common/TextFileLineUtil.java
 
b/ui/src/main/java/org/apache/hop/ui/pipeline/transform/common/TextFileLineUtil.java
index 18b4989be5..7a87e2ea65 100644
--- 
a/ui/src/main/java/org/apache/hop/ui/pipeline/transform/common/TextFileLineUtil.java
+++ 
b/ui/src/main/java/org/apache/hop/ui/pipeline/transform/common/TextFileLineUtil.java
@@ -361,11 +361,11 @@ public class TextFileLineUtil {
   }
 
   private static int matchChar(String pattern, int c, int index) {
-    return !pattern.isEmpty() && c == pattern.charAt(index) ? index + 1 : 0;
+    return pattern != null && !pattern.isEmpty() && c == pattern.charAt(index) 
? index + 1 : 0;
   }
 
   private static boolean isFullMatch(String pattern, int index) {
-    return !pattern.isEmpty() && index == pattern.length();
+    return pattern != null && !pattern.isEmpty() && index == pattern.length();
   }
 
   private static void handleBreaksInEnclosure(EnclosureData ed, int c) {
diff --git 
a/ui/src/test/java/org/apache/hop/ui/pipeline/transform/common/TextFileLineUtilTest.java
 
b/ui/src/test/java/org/apache/hop/ui/pipeline/transform/common/TextFileLineUtilTest.java
new file mode 100644
index 0000000000..eb0c112107
--- /dev/null
+++ 
b/ui/src/test/java/org/apache/hop/ui/pipeline/transform/common/TextFileLineUtilTest.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hop.ui.pipeline.transform.common;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import org.apache.hop.core.exception.HopFileException;
+import org.apache.hop.core.logging.ILogChannel;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+/** Tests for {@link TextFileLineUtil}, see issue #7244. */
+class TextFileLineUtilTest {
+
+  private static InputStreamReader reader(String content) {
+    return new InputStreamReader(
+        new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)), 
StandardCharsets.UTF_8);
+  }
+
+  private static String getLine(
+      String content, String enclosure, String escapeCharacter, boolean 
allowBreaks)
+      throws HopFileException {
+    return TextFileLineUtil.getLine(
+        Mockito.mock(ILogChannel.class),
+        reader(content),
+        TextFileLineUtil.FILE_FORMAT_UNIX,
+        new StringBuilder(),
+        enclosure,
+        escapeCharacter,
+        allowBreaks);
+  }
+
+  /**
+   * Issue #7244: with "break in enclosure" enabled and an empty (null) Escape 
character, reading a
+   * line threw a NullPointerException in {@code matchChar} ("pattern is 
null").
+   */
+  @Test
+  void getLineWithBreaksAndNullEscapeDoesNotThrow() throws Exception {
+    String line = getLine("\"a\";\"b\"\n", "\"", null, true);
+    assertEquals("\"a\";\"b\"", line);
+  }
+
+  /** An empty-string Escape character must behave the same as a null one. */
+  @Test
+  void getLineWithBreaksAndEmptyEscapeDoesNotThrow() throws Exception {
+    String line = getLine("\"a\";\"b\"\n", "\"", "", true);
+    assertEquals("\"a\";\"b\"", line);
+  }
+
+  /** A null enclosure combined with break-in-enclosure must not throw either. 
*/
+  @Test
+  void getLineWithBreaksAndNullEnclosureDoesNotThrow() throws Exception {
+    String line = getLine("a;b\n", null, null, true);
+    assertEquals("a;b", line);
+  }
+
+  /**
+   * A real line break inside an enclosed field must be kept on the same 
logical line when break in
+   * enclosure is enabled, even without an escape character.
+   */
+  @Test
+  void getLineKeepsBreakInsideEnclosureWithNullEscape() throws Exception {
+    // "2";"val02";"hello this is ""a""\ncarriage return"
+    String content = "\"2\";\"val02\";\"hello this is \"\"a\"\"\ncarriage 
return\"\nnext line\n";
+    String line = getLine(content, "\"", null, true);
+    assertEquals("\"2\";\"val02\";\"hello this is \"\"a\"\"\ncarriage 
return\"", line);
+  }
+
+  /** Reading past the end of the stream returns null (no trailing garbage). */
+  @Test
+  void getLineReturnsNullAtEndOfStream() throws Exception {
+    assertNull(getLine("", "\"", null, true));
+  }
+}

Reply via email to