Skip to content

Commit

Permalink
Support operations with empty formfield name
Browse files Browse the repository at this point in the history
DEVSIX-7308
  • Loading branch information
guustysebie committed Mar 5, 2023
1 parent 9521edf commit d8d330b
Show file tree
Hide file tree
Showing 17 changed files with 890 additions and 81 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.itextpdf.commons.utils;

import java.util.regex.Pattern;

public final class StringSplitUtil {

private StringSplitUtil() {

}


public static String[] splitKeepTrailingWhiteSpace(String data, char toSplitOn) {
return data.split(Pattern.quote(String.valueOf(toSplitOn)), -1);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,14 @@ This file is part of the iText (R) project.
@Category(UnitTest.class)
public class StringUtilTest extends ExtendedITextTest {

private static final char SPLIT_PERIOD = '.';

@Test
// Android-Conversion-Ignore-Test (TODO DEVSIX-6457 fix different behavior of Pattern.split method)
public void patternSplitTest01() {
// Pattern.split in Java works differently compared to Regex.Split in C#
// In C#, empty strings are possible at the beginning of the resultant array for non-capturing groups in split regex
// In C#, empty strings are possible at the beginning of the resultant array for non-capturing groups in
// split regex
// Thus, in C# we use a separate utility for splitting to align the implementation with Java
// This test verifies that the resultant behavior is the same
Pattern pattern = Pattern.compile("(?=[ab])");
Expand Down Expand Up @@ -96,4 +99,71 @@ public void stringSplitTest02() {
Assert.assertArrayEquals(expected, result);
}

@Test
public void splitKeepEmptyParts01() {
String source = "";
String[] expected = new String[]{
""
};
String[] result = StringSplitUtil.splitKeepTrailingWhiteSpace(source, SPLIT_PERIOD);
Assert.assertArrayEquals(source.split(String.valueOf(SPLIT_PERIOD)), result);
Assert.assertArrayEquals(expected, result);
}

@Test
public void splitKeepEmptyParts02() {
String source = null;
Assert.assertThrows(Exception.class,
() -> StringSplitUtil.splitKeepTrailingWhiteSpace(source, SPLIT_PERIOD));
}

@Test
public void splitKeepEmptyParts03() {
String source = "test.test1";
String[] expected = new String[] {"test", "test1"};
String[] result = StringSplitUtil.splitKeepTrailingWhiteSpace(source, SPLIT_PERIOD);
Assert.assertArrayEquals(expected, result);
}


@Test
public void splitKeepEmptyParts04() {
String source = "test..test1";
String[] expected = new String[] {"test", "", "test1"};
String[] result = StringSplitUtil.splitKeepTrailingWhiteSpace(source, SPLIT_PERIOD);
Assert.assertArrayEquals(expected, result);
}

@Test
public void splitKeepEmptyParts05() {
String source = "test...test1";
String[] expected = new String[] {"test", "", "", "test1"};
String[] result = StringSplitUtil.splitKeepTrailingWhiteSpace(source, SPLIT_PERIOD);
Assert.assertArrayEquals(expected, result);
}

@Test
public void splitKeepEmptyParts06() {
String source = ".test1";
String[] expected = new String[] {"", "test1"};
String[] result = StringSplitUtil.splitKeepTrailingWhiteSpace(source, SPLIT_PERIOD);
Assert.assertArrayEquals(expected, result);
}

@Test
public void splitKeepEmptyPartsDifferentBehaviour01() {
String source = "test.";
String[] expected = new String[] {"test", ""};
String[] result = StringSplitUtil.splitKeepTrailingWhiteSpace(source, SPLIT_PERIOD);
Assert.assertArrayEquals(expected, result);
}

@Test
public void splitKeepEmptyPartsDifferentBehaviour02() {
String source = "test..";
String[] expected = new String[] {"test", "", ""};
String[] result = StringSplitUtil.splitKeepTrailingWhiteSpace(source, SPLIT_PERIOD);
Assert.assertArrayEquals(expected, result);
}

}
78 changes: 33 additions & 45 deletions forms/src/main/java/com/itextpdf/forms/PdfAcroForm.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ This file is part of the iText (R) project.
package com.itextpdf.forms;

import com.itextpdf.commons.utils.MessageFormatUtil;
import com.itextpdf.commons.utils.StringSplitUtil;
import com.itextpdf.forms.exceptions.FormsExceptionMessageConstant;
import com.itextpdf.forms.fields.AbstractPdfFormField;
import com.itextpdf.forms.fields.PdfFormAnnotation;
Expand Down Expand Up @@ -77,7 +78,6 @@ This file is part of the iText (R) project.

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
Expand Down Expand Up @@ -213,7 +213,7 @@ public static PdfAcroForm getAcroForm(PdfDocument document, boolean createIfNotE
* @param field the {@link PdfFormField} to be added to the form
*/
public void addField(PdfFormField field) {
if (field.getFieldName() == null) {
if (!field.getPdfObject().containsKey(PdfName.T)) {
throw new PdfException(FormsExceptionMessageConstant.FORM_FIELD_MUST_HAVE_A_NAME);
}

Expand All @@ -234,7 +234,7 @@ public void addField(PdfFormField field) {
* in case they have the same names
*/
public void addField(PdfFormField field, PdfPage page, boolean replaceExisted) {
if (field.getFieldName() == null) {
if (!field.getPdfObject().containsKey(PdfName.T)) {
throw new PdfException(FormsExceptionMessageConstant.FORM_FIELD_MUST_HAVE_A_NAME);
}

Expand Down Expand Up @@ -327,14 +327,13 @@ public Map<String, PdfFormField> getAllFormFields() {
if (fields.size() == 0) {
fields = populateFormFieldsMap();
}
Map<String, PdfFormField> allFields = new HashMap<>();
allFields.putAll(fields);
final Map<String, PdfFormField> allFields = new LinkedHashMap<>(fields);
for (Entry<String, PdfFormField> field : fields.entrySet()) {
List<PdfFormField> kids = field.getValue().getAllChildFormFields();
final List<PdfFormField> kids = field.getValue().getAllChildFormFields();
for (PdfFormField kid : kids) {
//TODO DEVSIX-7308 Handle form fields without names more carefully
if (kid.getFieldName() != null) {
allFields.put(kid.getFieldName().toUnicodeString(), kid);
final PdfString kidFieldName = kid.getFieldName();
if (kidFieldName != null) {
allFields.put(kidFieldName.toUnicodeString(), kid);
}
}
}
Expand Down Expand Up @@ -470,10 +469,11 @@ public PdfAcroForm setSignatureFlag(int sigFlag) {
*/
public int getSignatureFlags() {
PdfNumber f = getPdfObject().getAsNumber(PdfName.SigFlags);
if (f != null)
return f.intValue();
else
if (f == null) {
return 0;
} else {
return f.intValue();
}
}

/**
Expand Down Expand Up @@ -649,14 +649,17 @@ public PdfFormField getField(String fieldName) {
if (fields.get(fieldName) != null) {
return fields.get(fieldName);
}
String[] splitFields = fieldName.split("\\.");
PdfFormField parentFormField = fields.get(splitFields[0]);
final String[] splitFieldsArray = StringSplitUtil.splitKeepTrailingWhiteSpace(fieldName, '.');
if (splitFieldsArray.length == 0) {
return null;
}
PdfFormField parentFormField = fields.get(splitFieldsArray[0]);
PdfFormField kidField = parentFormField;
for (int i = 1; i < splitFields.length; i++) {
if (parentFormField.isFlushed()) {
for (int i = 1; i < splitFieldsArray.length; i++) {
if (parentFormField == null || parentFormField.isFlushed()) {
return null;
}
kidField = parentFormField.getChildField(splitFields[i]);
kidField = parentFormField.getChildField(splitFieldsArray[i]);
parentFormField = kidField;
}
return kidField;
Expand Down Expand Up @@ -697,7 +700,6 @@ public void setGenerateAppearance(boolean generateAppearance) {
}
this.generateAppearance = generateAppearance;
}

/**
* Flattens interactive {@link PdfFormField form field}s in the document. If
* no fields have been explicitly included via {@link #partialFormFlattening},
Expand All @@ -711,8 +713,6 @@ public void flattenFields() {
Set<PdfFormField> fields;
if (fieldsForFlattening.isEmpty()) {
this.fields.clear();
//This alternate addition of fields is used due to incorrect handling of fields without names.
//Must be replaced with getAllFormFields() after DEVSIX-7308
fields = getAllFormFieldsWithoutNames();
} else {
fields = new LinkedHashSet<>();
Expand Down Expand Up @@ -896,6 +896,12 @@ public void partialFormFlattening(String fieldName) {
* @param newName the new name of the field. Must not be used currently.
*/
public void renameField(String oldName, String newName) {
final PdfFormField oldField = getField(oldName);
if (oldField == null) {
LOGGER.warn(MessageFormatUtil.format(
FormsLogMessageConstants.FIELDNAME_NOT_FOUND_OPERATION_CAN_NOT_BE_COMPLETED, oldName));
return;
}
getField(oldName).setFieldName(newName);
PdfFormField field = fields.get(oldName);
if (field != null) {
Expand Down Expand Up @@ -969,7 +975,6 @@ protected boolean isWrappedObjectMustBeIndirect() {
private Map<String, PdfFormField> populateFormFieldsMap() {
final PdfArray rawFields = getFields();
Map<String, PdfFormField> fields = new LinkedHashMap<>();
int index = 1;
final PdfArray shouldBeRemoved = new PdfArray();
for (PdfObject field : rawFields) {
if (field.isFlushed()) {
Expand All @@ -987,32 +992,15 @@ private Map<String, PdfFormField> populateFormFieldsMap() {
PdfFormFieldMergeUtil.mergeKidsWithSameNames(formField, false);

PdfString fieldName = formField.getFieldName();
String name;
if (fieldName == null) {
while (fieldName == null) {
if (formField.getParent() == null) {
LOGGER.warn(MessageFormatUtil.format(FormsLogMessageConstants.CANNOT_CREATE_FORMFIELD,
field.getIndirectReference()));
break;
}
if (fieldName != null) {
String name = formField.getFieldName().toUnicodeString();

PdfFormField parentField = PdfFormField.makeFormField(formField.getParent(), document);
fieldName = parentField.getFieldName();
}
if (fieldName == null) {
continue;
if (formField.isInReadingMode() || !fields.containsKey(name) ||
!PdfFormFieldMergeUtil.mergeTwoFieldsWithTheSameNames(fields.get(name), formField, true)) {
fields.put(name, formField);
} else {
shouldBeRemoved.add(field);
}
name = fieldName.toUnicodeString() + "." + index;
index++;
} else {
name = fieldName.toUnicodeString();
}

if (formField.isInReadingMode() || !fields.containsKey(name) ||
!PdfFormFieldMergeUtil.mergeTwoFieldsWithTheSameNames(fields.get(name), formField, true)) {
fields.put(name, formField);
} else {
shouldBeRemoved.add(field);
}
}
for (PdfObject field : shouldBeRemoved) {
Expand Down
12 changes: 9 additions & 3 deletions forms/src/main/java/com/itextpdf/forms/PdfPageFormCopier.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ This file is part of the iText (R) project.
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.PdfString;
import com.itextpdf.kernel.pdf.annot.PdfAnnotation;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -224,14 +225,19 @@ private void copyParentFormField(PdfPage toPage, Map<String, PdfFormField> field

private PdfFormField mergeFieldsWithTheSameName(AbstractPdfFormField newField) {
PdfString fieldName = newField.getPdfObject().getAsString(PdfName.T);

PdfDictionary parent = newField.getParent();
if (parent != null) {
newField.setParent(PdfFormField.makeFormField(parent, newField.getDocument()));
if (null == fieldName) {
fieldName = parent.getAsString(PdfName.T);
if (fieldName == null) {
if (newField.isTerminalFormField()) {
fieldName = new PdfString(parent.getAsString(PdfName.T).toUnicodeString() + ".");
} else {
fieldName = parent.getAsString(PdfName.T);
}
}
}

String fullFieldName = fieldName.toUnicodeString();
if (null != newField.getFieldName()) {
fullFieldName = newField.getFieldName().toUnicodeString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,16 @@ public final class FormsExceptionMessageConstant {
public static final String CANNOT_MERGE_FORMFIELDS = "Cannot merge form fields with the same names. Partial name " +
"is {0}. Field dictionaries with the same fully qualified field name shall have the same field type (FT), "
+ "value (V), and default value (DV).";

public static final String FIELD_FLATTENING_IS_NOT_SUPPORTED_IN_APPEND_MODE = "Field flattening is not supported "
+ "in append mode.";

public static final String INNER_ARRAY_SHALL_HAVE_TWO_ELEMENTS = "Inner arrays shall have exactly two elements";

public static final String PAGE_ALREADY_FLUSHED_USE_ADD_FIELD_APPEARANCE_TO_PAGE_METHOD_BEFORE_PAGE_FLUSHING = ""
+ "The page has been already flushed. Use PdfAcroForm#addFieldAppearanceToPage() method before page "
+ "flushing.";

public static final String FORM_FIELD_MUST_HAVE_A_NAME = "Form field must have a name."
+ " Set it using PdfFormField#setFieldName call.";

Expand Down
Loading

0 comments on commit d8d330b

Please sign in to comment.