From d8d330b910271f77e1073dd123ab4ad34cf1a539 Mon Sep 17 00:00:00 2001 From: Guust Ysebie Date: Tue, 14 Feb 2023 11:08:08 +0100 Subject: [PATCH] Support operations with empty formfield name DEVSIX-7308 --- .../commons/utils/StringSplitUtil.java | 16 + .../commons/utils/StringUtilTest.java | 72 +- .../java/com/itextpdf/forms/PdfAcroForm.java | 78 +-- .../com/itextpdf/forms/PdfPageFormCopier.java | 12 +- .../FormsExceptionMessageConstant.java | 4 + .../forms/fields/AbstractPdfFormField.java | 31 +- .../itextpdf/forms/fields/PdfFormField.java | 15 +- .../forms/fields/PdfFormFieldMergeUtil.java | 20 +- .../forms/logs/FormsLogMessageConstants.java | 3 + .../forms/PdfAcroFormIntegrationTest.java | 4 +- .../com/itextpdf/forms/PdfAcroFormTest.java | 43 +- .../com/itextpdf/forms/PdfFormCopyTest.java | 2 +- .../com/itextpdf/forms/PdfFormFieldTest.java | 20 +- .../forms/fields/PdfFormFieldNameTest.java | 651 ++++++++++++++++++ .../PdfFormCopyTest/cmp_copyFields07.pdf | Bin 110128 -> 110605 bytes .../cmp_fillUnmergedTextFormField.pdf | Bin 14931 -> 14943 bytes .../cmp_maxLenDeepInheritanceTest.pdf | Bin 10227 -> 10191 bytes 17 files changed, 890 insertions(+), 81 deletions(-) create mode 100644 commons/src/main/java/com/itextpdf/commons/utils/StringSplitUtil.java create mode 100644 forms/src/test/java/com/itextpdf/forms/fields/PdfFormFieldNameTest.java diff --git a/commons/src/main/java/com/itextpdf/commons/utils/StringSplitUtil.java b/commons/src/main/java/com/itextpdf/commons/utils/StringSplitUtil.java new file mode 100644 index 0000000000..a385ec82b1 --- /dev/null +++ b/commons/src/main/java/com/itextpdf/commons/utils/StringSplitUtil.java @@ -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); + } + +} diff --git a/commons/src/test/java/com/itextpdf/commons/utils/StringUtilTest.java b/commons/src/test/java/com/itextpdf/commons/utils/StringUtilTest.java index 4a3c2e129f..ff8315a3a8 100644 --- a/commons/src/test/java/com/itextpdf/commons/utils/StringUtilTest.java +++ b/commons/src/test/java/com/itextpdf/commons/utils/StringUtilTest.java @@ -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])"); @@ -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); + } + } diff --git a/forms/src/main/java/com/itextpdf/forms/PdfAcroForm.java b/forms/src/main/java/com/itextpdf/forms/PdfAcroForm.java index 18c6a9ce9b..8100476819 100644 --- a/forms/src/main/java/com/itextpdf/forms/PdfAcroForm.java +++ b/forms/src/main/java/com/itextpdf/forms/PdfAcroForm.java @@ -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; @@ -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; @@ -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); } @@ -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); } @@ -327,14 +327,13 @@ public Map getAllFormFields() { if (fields.size() == 0) { fields = populateFormFieldsMap(); } - Map allFields = new HashMap<>(); - allFields.putAll(fields); + final Map allFields = new LinkedHashMap<>(fields); for (Entry field : fields.entrySet()) { - List kids = field.getValue().getAllChildFormFields(); + final List 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); } } } @@ -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(); + } } /** @@ -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; @@ -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}, @@ -711,8 +713,6 @@ public void flattenFields() { Set 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<>(); @@ -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) { @@ -969,7 +975,6 @@ protected boolean isWrappedObjectMustBeIndirect() { private Map populateFormFieldsMap() { final PdfArray rawFields = getFields(); Map fields = new LinkedHashMap<>(); - int index = 1; final PdfArray shouldBeRemoved = new PdfArray(); for (PdfObject field : rawFields) { if (field.isFlushed()) { @@ -987,32 +992,15 @@ private Map 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) { diff --git a/forms/src/main/java/com/itextpdf/forms/PdfPageFormCopier.java b/forms/src/main/java/com/itextpdf/forms/PdfPageFormCopier.java index dc38dc6eab..80cf10e3fd 100644 --- a/forms/src/main/java/com/itextpdf/forms/PdfPageFormCopier.java +++ b/forms/src/main/java/com/itextpdf/forms/PdfPageFormCopier.java @@ -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; @@ -224,14 +225,19 @@ private void copyParentFormField(PdfPage toPage, Map 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(); diff --git a/forms/src/main/java/com/itextpdf/forms/exceptions/FormsExceptionMessageConstant.java b/forms/src/main/java/com/itextpdf/forms/exceptions/FormsExceptionMessageConstant.java index bd612756ed..467f8478bb 100644 --- a/forms/src/main/java/com/itextpdf/forms/exceptions/FormsExceptionMessageConstant.java +++ b/forms/src/main/java/com/itextpdf/forms/exceptions/FormsExceptionMessageConstant.java @@ -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."; diff --git a/forms/src/main/java/com/itextpdf/forms/fields/AbstractPdfFormField.java b/forms/src/main/java/com/itextpdf/forms/fields/AbstractPdfFormField.java index 6a9abb33f2..d143a1231b 100644 --- a/forms/src/main/java/com/itextpdf/forms/fields/AbstractPdfFormField.java +++ b/forms/src/main/java/com/itextpdf/forms/fields/AbstractPdfFormField.java @@ -61,6 +61,7 @@ This file is part of the iText (R) project. import com.itextpdf.kernel.pdf.PdfString; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -75,6 +76,7 @@ This file is part of the iText (R) project. */ public abstract class AbstractPdfFormField extends PdfObjectWrapper { + private static final PdfName[] TERMINAL_FIELDS = new PdfName[]{PdfName.Btn, PdfName.Tx, PdfName.Ch, PdfName.Sig}; /** * Size of text in form fields when font size is not explicitly set. */ @@ -189,9 +191,12 @@ public PdfString getFieldName() { } PdfString name = getPdfObject().getAsString(PdfName.T); if (name != null) { - name = new PdfString(parentName + name.toUnicodeString(), PdfEncodings.UNICODE_BIG); + return new PdfString(parentName + name.toUnicodeString(), PdfEncodings.UNICODE_BIG); } - return name; + if (isTerminalFormField()) { + return new PdfString(parentName, PdfEncodings.UNICODE_BIG); + } + return null; } /** @@ -259,6 +264,7 @@ public PdfAConformanceLevel getPdfAConformanceLevel() { * Sets the text color and does not regenerate appearance stream. * * @param color the new value for the Color. + * * @return the edited field. */ void setColorNoRegenerate(Color color) { @@ -277,8 +283,9 @@ void setColorNoRegenerate(Color color) { * If the key is already present in this field dictionary, * this method will override the old value with the specified one. * - * @param key key to insert or to override. + * @param key key to insert or to override. * @param value the value to associate with the specified key. + * * @return the edited field. */ public AbstractPdfFormField put(PdfName key, PdfObject value) { @@ -291,6 +298,7 @@ public AbstractPdfFormField put(PdfName key, PdfObject value) { * Removes the specified key from the {@link PdfDictionary} of this field. * * @param key key to be removed. + * * @return the edited field. */ public AbstractPdfFormField remove(PdfName key) { @@ -333,6 +341,7 @@ protected boolean isWrappedObjectMustBeIndirect() { * Sets the text color and regenerates appearance stream. * * @param color the new value for the Color. + * * @return the edited {@link AbstractPdfFormField}. */ public AbstractPdfFormField setColor(Color color) { @@ -348,6 +357,7 @@ public AbstractPdfFormField setColor(Color color) { * if it's a pdf/a document. * * @param font The new font to be set. + * * @return The edited {@link AbstractPdfFormField}. */ public AbstractPdfFormField setFont(PdfFont font) { @@ -361,6 +371,7 @@ public AbstractPdfFormField setFont(PdfFont font) { * field appearance after setting the new value. * * @param fontSize The new font size to be set. + * * @return The edited {@link AbstractPdfFormField}. */ public AbstractPdfFormField setFontSize(float fontSize) { @@ -374,6 +385,7 @@ public AbstractPdfFormField setFontSize(float fontSize) { * field appearance after setting the new value. * * @param fontSize The new font size to be set. + * * @return The edited {@link AbstractPdfFormField}. */ public AbstractPdfFormField setFontSize(int fontSize) { @@ -399,6 +411,7 @@ public AbstractPdfFormField setFontSizeAutoScale() { * * @param font The new font to be set. * @param fontSize The new font size to be set. + * * @return The edited {@link AbstractPdfFormField}. */ public AbstractPdfFormField setFontAndSize(PdfFont font, float fontSize) { @@ -407,6 +420,18 @@ public AbstractPdfFormField setFontAndSize(PdfFont font, float fontSize) { return this; } + public boolean isTerminalFormField() { + if (getPdfObject() == null || getPdfObject().get(PdfName.FT) == null) { + return false; + } + for (PdfName terminalField : TERMINAL_FIELDS) { + if (terminalField.equals(getPdfObject().get(PdfName.FT))) { + return true; + } + } + return false; + } + void updateFontAndFontSize(PdfFont font, float fontSize) { this.font = font; this.fontSize = fontSize; diff --git a/forms/src/main/java/com/itextpdf/forms/fields/PdfFormField.java b/forms/src/main/java/com/itextpdf/forms/fields/PdfFormField.java index 36e42b04d6..ea137a1fcb 100644 --- a/forms/src/main/java/com/itextpdf/forms/fields/PdfFormField.java +++ b/forms/src/main/java/com/itextpdf/forms/fields/PdfFormField.java @@ -401,9 +401,7 @@ public PdfFormField setValue(String value, boolean generateAppearance) { if (parent == null) { setFieldValue(value, generateAppearance); } else { - PdfString partialFieldName = getPartialFieldName(); - //TODO Remove this check after DEVSIX-7308 ticket will be closed. - String fieldName = partialFieldName == null ? "" : partialFieldName.toUnicodeString(); + String fieldName = getPartialFieldName().toUnicodeString(); for (PdfFormField field : parent.getChildFormFields()) { if (fieldName.equals(field.getPartialFieldName().toUnicodeString())) { @@ -634,11 +632,12 @@ public PdfFormField setFieldName(String name) { /** * Gets the current field partial name. * - * @return the current field partial name, as a {@link PdfString}. + * @return the current field partial name, as a {@link PdfString}. If the field has no partial name, + * an empty {@link PdfString} is returned. */ public PdfString getPartialFieldName() { - //TODO DEVSIX-7308 Handle form fields without names more carefully - return getPdfObject().getAsString(PdfName.T); + PdfString partialName = getPdfObject().getAsString(PdfName.T); + return partialName == null ? new PdfString("") : partialName; } /** @@ -1433,9 +1432,9 @@ private boolean mergeKidsIfKidWithSuchNameExists(AbstractPdfFormField newKid, bo if (isInReadingMode() || PdfFormAnnotationUtil.isPureWidget(newKid.getPdfObject())) { return false; } - String newKidPartialName = PdfFormFieldMergeUtil.getPartialName(newKid.getPdfObject()); + String newKidPartialName = PdfFormFieldMergeUtil.getPartialName(newKid); for (AbstractPdfFormField kid : childFields) { - String kidPartialName = PdfFormFieldMergeUtil.getPartialName(kid.getPdfObject()); + String kidPartialName = PdfFormFieldMergeUtil.getPartialName(kid); if (kidPartialName != null && kidPartialName.equals(newKidPartialName)) { // Merge kid with the first found field with the same name. return PdfFormFieldMergeUtil.mergeTwoFieldsWithTheSameNames((PdfFormField) kid, (PdfFormField) newKid, diff --git a/forms/src/main/java/com/itextpdf/forms/fields/PdfFormFieldMergeUtil.java b/forms/src/main/java/com/itextpdf/forms/fields/PdfFormFieldMergeUtil.java index 03fce9d02d..b2191f55c4 100644 --- a/forms/src/main/java/com/itextpdf/forms/fields/PdfFormFieldMergeUtil.java +++ b/forms/src/main/java/com/itextpdf/forms/fields/PdfFormFieldMergeUtil.java @@ -51,7 +51,6 @@ This file is part of the iText (R) project. import com.itextpdf.kernel.pdf.PdfDictionary; import com.itextpdf.kernel.pdf.PdfName; import com.itextpdf.kernel.pdf.PdfObject; -import com.itextpdf.kernel.pdf.PdfString; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -93,7 +92,7 @@ public static void mergeKidsWithSameNames(PdfFormField parentField, boolean thro // Try to merge for the kid mergeKidsWithSameNames((PdfFormField) kid, throwExceptionOnError); - String kidName = getPartialName(kid.getPdfObject()); + String kidName = getPartialName(kid); if (!addedKids.containsKey(kidName) || !mergeTwoFieldsWithTheSameNames( (PdfFormField) addedKids.get(kidName), (PdfFormField) kid, throwExceptionOnError)) { addedKids.put(kidName, kid); @@ -146,18 +145,19 @@ public static boolean mergeTwoFieldsWithTheSameNames(PdfFormField firstField, Pd /** * Gets partial name for the field dictionary. * - * @param fieldDict field dictionary to get name. + * @param field field to get name from. * * @return field partial name. Also, null if passed dictionary is a pure widget, * empty string in case it is a field with no /T entry. */ - // TODO This method usages should be replaced by PdfFormField#getPartialFieldName after DEVSIX-7308 is closed. - public static String getPartialName(PdfDictionary fieldDict) { - if (PdfFormAnnotationUtil.isPureWidget(fieldDict)) { + public static String getPartialName(AbstractPdfFormField field) { + if (PdfFormAnnotationUtil.isPureWidget(field.getPdfObject())) { return null; } - PdfString partialName = fieldDict.getAsString(PdfName.T); - return partialName == null ? "" : partialName.toUnicodeString(); + if (field instanceof PdfFormField) { + return ((PdfFormField) field).getPartialFieldName().toUnicodeString(); + } + return ""; } /** @@ -186,6 +186,10 @@ public static void processDirtyAnnotations(PdfFormField parentField, boolean thr // If not - go over all fields to compare with parent's fields if (!(PdfName.Btn.equals(parentField.getFormType()) && parentField.getFieldFlag(PdfButtonFormField.FF_RADIO))) { + if (formDict.containsKey(PdfName.T)) { + // We only want to perform the merge if field doesn't contain any name (even empty one) + continue; + } for (final PdfName key : formDict.keySet()) { // Everything except Parent and Kids must be identical to allow the merge if (!PdfName.Parent.equals(key) && !PdfName.Kids.equals(key) && diff --git a/forms/src/main/java/com/itextpdf/forms/logs/FormsLogMessageConstants.java b/forms/src/main/java/com/itextpdf/forms/logs/FormsLogMessageConstants.java index f629e1db89..84ffeead5f 100644 --- a/forms/src/main/java/com/itextpdf/forms/logs/FormsLogMessageConstants.java +++ b/forms/src/main/java/com/itextpdf/forms/logs/FormsLogMessageConstants.java @@ -51,6 +51,9 @@ public final class FormsLogMessageConstants { public static final String N_ENTRY_IS_REQUIRED_FOR_APPEARANCE_DICTIONARY = "\\N entry is required to be present in an appearance dictionary."; + public static final String FIELDNAME_NOT_FOUND_OPERATION_CAN_NOT_BE_COMPLETED = + "Fieldname: <{0}> not found. Operation can not be completed."; + private FormsLogMessageConstants() { } } diff --git a/forms/src/test/java/com/itextpdf/forms/PdfAcroFormIntegrationTest.java b/forms/src/test/java/com/itextpdf/forms/PdfAcroFormIntegrationTest.java index d04faffd8b..5b91946d09 100644 --- a/forms/src/test/java/com/itextpdf/forms/PdfAcroFormIntegrationTest.java +++ b/forms/src/test/java/com/itextpdf/forms/PdfAcroFormIntegrationTest.java @@ -54,12 +54,10 @@ public static void beforeClass() { } @Test - @LogMessages(messages = {@LogMessage(messageTemplate = FormsLogMessageConstants.CANNOT_CREATE_FORMFIELD)}) public void orphanedNamelessFormFieldTest() throws IOException { try (PdfDocument pdfDoc = new PdfDocument(new PdfReader(SOURCE_FOLDER + "orphanedFormField.pdf"))) { PdfAcroForm form = PdfAcroForm.getAcroForm(pdfDoc, true); - // 2 out of 3 have been gathered - Assert.assertEquals(2, form.getDirectFormFields().size()); + Assert.assertEquals(3, form.getDirectFormFields().size()); } } diff --git a/forms/src/test/java/com/itextpdf/forms/PdfAcroFormTest.java b/forms/src/test/java/com/itextpdf/forms/PdfAcroFormTest.java index 0181a6815d..836d9565e2 100644 --- a/forms/src/test/java/com/itextpdf/forms/PdfAcroFormTest.java +++ b/forms/src/test/java/com/itextpdf/forms/PdfAcroFormTest.java @@ -305,7 +305,7 @@ public void fieldKidsWithTheSameNamesTest() { @Test public void namelessFieldTest() { - try(PdfDocument outputDoc = createDocument()) { + try (PdfDocument outputDoc = createDocument()) { outputDoc.addNewPage(); PdfAcroForm acroForm = PdfAcroForm.getAcroForm(outputDoc, true); PdfDictionary fieldDict = new PdfDictionary(); @@ -416,7 +416,41 @@ public void addRootFieldWithMergedFieldKidTest() { } @Test - public void addRootFieldWithDirtyAnnotationsTest() { + public void addRootFieldWithDirtyNamedAnnotationsTest() { + try (PdfDocument outputDoc = createDocument()) { + outputDoc.addNewPage(); + PdfAcroForm acroForm = PdfAcroForm.getAcroForm(outputDoc, true); + + PdfFormField rootField = new TextFormFieldBuilder(outputDoc, "root") + .createText().setValue("root"); + PdfFormField firstDirtyAnnot = new TextFormFieldBuilder(outputDoc, "root") + .setWidgetRectangle(new Rectangle(100, 500, 200, 30)) + .createText(); + firstDirtyAnnot.getPdfObject().remove(PdfName.V); + PdfFormField secondDirtyAnnot = new TextFormFieldBuilder(outputDoc, "root") + .setWidgetRectangle(new Rectangle(200, 600, 300, 40)) + .createText(); + secondDirtyAnnot.getPdfObject().remove(PdfName.V); + + rootField.addKid(firstDirtyAnnot); + rootField.addKid(secondDirtyAnnot); + + Assert.assertEquals(1, rootField.getKids().size()); + Assert.assertEquals(2, firstDirtyAnnot.getKids().size()); + + acroForm.addField(rootField); + + Assert.assertEquals(1, acroForm.getFields().size()); + + PdfArray fieldKids = acroForm.getField("root").getKids(); + Assert.assertEquals(1, fieldKids.size()); + + Assert.assertFalse(PdfFormAnnotationUtil.isPureWidget((PdfDictionary) fieldKids.get(0))); + } + } + + @Test + public void addRootFieldWithDirtyUnnamedAnnotationsTest() { try (PdfDocument outputDoc = createDocument()) { outputDoc.addNewPage(); PdfAcroForm acroForm = PdfAcroForm.getAcroForm(outputDoc, true); @@ -427,10 +461,15 @@ public void addRootFieldWithDirtyAnnotationsTest() { .setWidgetRectangle(new Rectangle(100, 500, 200, 30)) .createText(); firstDirtyAnnot.getPdfObject().remove(PdfName.V); + // Remove name in order to make dirty annotation being merged + firstDirtyAnnot.getPdfObject().remove(PdfName.T); + PdfFormField secondDirtyAnnot = new TextFormFieldBuilder(outputDoc, "root") .setWidgetRectangle(new Rectangle(200, 600, 300, 40)) .createText(); secondDirtyAnnot.getPdfObject().remove(PdfName.V); + // Remove name in order to make dirty annotation being merged + secondDirtyAnnot.getPdfObject().remove(PdfName.T); rootField.addKid(firstDirtyAnnot); rootField.addKid(secondDirtyAnnot); diff --git a/forms/src/test/java/com/itextpdf/forms/PdfFormCopyTest.java b/forms/src/test/java/com/itextpdf/forms/PdfFormCopyTest.java index cd4804b281..fedefd745c 100644 --- a/forms/src/test/java/com/itextpdf/forms/PdfFormCopyTest.java +++ b/forms/src/test/java/com/itextpdf/forms/PdfFormCopyTest.java @@ -329,7 +329,7 @@ public void copyFieldsTest06() throws IOException, InterruptedException { @Test @LogMessages(messages = { - @LogMessage(messageTemplate = IoLogMessageConstant.DOCUMENT_ALREADY_HAS_FIELD, count = 32) + @LogMessage(messageTemplate = IoLogMessageConstant.DOCUMENT_ALREADY_HAS_FIELD, count = 13) }) public void copyFieldsTest07() throws IOException, InterruptedException { String srcFilename = sourceFolder + "datasheet.pdf"; diff --git a/forms/src/test/java/com/itextpdf/forms/PdfFormFieldTest.java b/forms/src/test/java/com/itextpdf/forms/PdfFormFieldTest.java index b7e1f03242..bf6c279bd4 100644 --- a/forms/src/test/java/com/itextpdf/forms/PdfFormFieldTest.java +++ b/forms/src/test/java/com/itextpdf/forms/PdfFormFieldTest.java @@ -999,7 +999,6 @@ public void maxLenInheritanceTest() throws IOException, InterruptedException { } @Test - @Ignore("DEVSIX-7308 update cmp files after the ticket will be resolved") public void maxLenDeepInheritanceTest() throws IOException, InterruptedException { String srcFilename = sourceFolder + "maxLenDeepInheritanceTest.pdf"; String destFilename = destinationFolder + "maxLenDeepInheritanceTest.pdf"; @@ -1008,8 +1007,7 @@ public void maxLenDeepInheritanceTest() throws IOException, InterruptedException PdfDocument destDoc = new PdfDocument(new PdfReader(srcFilename), new PdfWriter(destFilename)); PdfAcroForm acroForm = PdfAcroForm.getAcroForm(destDoc, false); - // TODO After DEVSIX-7308 getField should return field without partial name here - acroForm.getField("text.1.").setValue("WoOooOw"); + acroForm.getField("text.1.").setColor(ColorConstants.RED); destDoc.close(); @@ -1330,7 +1328,6 @@ public void appendModeAppearance() throws IOException, InterruptedException { } @Test - //TODO DEVSIX-7308 Handle form fields without names more carefully public void fillUnmergedTextFormField() throws IOException, InterruptedException { String file = sourceFolder + "fillUnmergedTextFormField.pdf"; String outfile = destinationFolder + "fillUnmergedTextFormField.pdf"; @@ -1338,7 +1335,6 @@ public void fillUnmergedTextFormField() throws IOException, InterruptedException PdfDocument pdfDocument = new PdfDocument(new PdfReader(file), new PdfWriter(outfile)); fillAcroForm(pdfDocument, text); - pdfDocument.close(); Assert.assertNull(new CompareTool().compareByContent(destinationFolder + "fillUnmergedTextFormField.pdf", @@ -1493,7 +1489,8 @@ public void duplicateFormTest() throws IOException, InterruptedException { pdfInnerDoc.close(); pdfDocument.close(); - pdfDocument = new PdfDocument(new PdfReader(new ByteArrayInputStream(byteArrayOutputStream.toByteArray())), new PdfWriter(outPdf)); + pdfDocument = new PdfDocument(new PdfReader(new ByteArrayInputStream(byteArrayOutputStream.toByteArray())), + new PdfWriter(outPdf)); PdfAcroForm pdfAcroForm = PdfAcroForm.getAcroForm(pdfDocument, false); pdfAcroForm.getField("checkbox").setValue("Off"); pdfDocument.close(); @@ -1509,7 +1506,7 @@ public void getValueTest() throws IOException, InterruptedException { try (PdfDocument doc = new PdfDocument(new PdfReader(srcPdf), new PdfWriter(outPdf))) { PdfAcroForm acroForm = PdfAcroForm.getAcroForm(doc, false); for (AbstractPdfFormField field : acroForm.getAllFormFieldsAndAnnotations()) { - if (field instanceof PdfFormField && field.getPdfObject().get(PdfName.V).toString() == "child") { + if (field instanceof PdfFormField && "child".equals(field.getPdfObject().get(PdfName.V).toString())) { // Child has value "root" still because it doesn't contain T entry Assert.assertEquals("root", ((PdfFormField) field).getValue().toString()); } @@ -1519,4 +1516,13 @@ public void getValueTest() throws IOException, InterruptedException { Assert.assertNull(new CompareTool().compareByContent(outPdf, cmpPdf, destinationFolder, "diff_")); } + + @Test + public void getSigFlagsTest() { + try (PdfDocument doc = new PdfDocument(new PdfWriter(new ByteArrayOutputStream()))) { + PdfAcroForm form = PdfAcroForm.getAcroForm(doc, true); + form.setSignatureFlag(1); + Assert.assertEquals(1, form.getSignatureFlags()); + } + } } diff --git a/forms/src/test/java/com/itextpdf/forms/fields/PdfFormFieldNameTest.java b/forms/src/test/java/com/itextpdf/forms/fields/PdfFormFieldNameTest.java new file mode 100644 index 0000000000..9e45c1fde8 --- /dev/null +++ b/forms/src/test/java/com/itextpdf/forms/fields/PdfFormFieldNameTest.java @@ -0,0 +1,651 @@ +package com.itextpdf.forms.fields; + + +import com.itextpdf.forms.PdfAcroForm; +import com.itextpdf.forms.PdfPageFormCopier; +import com.itextpdf.forms.logs.FormsLogMessageConstants; +import com.itextpdf.kernel.geom.Rectangle; +import com.itextpdf.kernel.pdf.PdfDocument; +import com.itextpdf.kernel.pdf.PdfReader; +import com.itextpdf.kernel.pdf.PdfWriter; +import com.itextpdf.test.ExtendedITextTest; +import com.itextpdf.test.annotations.LogMessage; +import com.itextpdf.test.annotations.LogMessages; +import com.itextpdf.test.annotations.type.UnitTest; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +@Category(UnitTest.class) +public class PdfFormFieldNameTest extends ExtendedITextTest { + + private PdfDocument outputDoc; + private PdfAcroForm acroForm; + + @Before + public void init() { + outputDoc = new PdfDocument(new PdfWriter(new ByteArrayOutputStream())); + outputDoc.addNewPage(); + acroForm = PdfAcroForm.getAcroForm(outputDoc, true); + } + + @After + public void shutdown() { + outputDoc.close(); + } + + @Test + public void getFormFieldWithNormalNames() { + //ARRANGE + PdfFormField root = addDefaultTextFormField(outputDoc, "root"); + PdfFormField child1 = addDefaultTextFormField(outputDoc, "child1"); + PdfFormField child2 = addDefaultTextFormField(outputDoc, "child2"); + child1.addKid(child2); + root.addKid(child1); + acroForm.addField(root); + + //ACT + PdfFormField rootRecoveredField = acroForm.getField("root"); + PdfFormField child1RecoveredField = acroForm.getField("root.child1"); + PdfFormField child2RecoveredField = acroForm.getField("root.child1.child2"); + //ASSERT + + Assert.assertEquals(root, rootRecoveredField); + Assert.assertEquals("root", rootRecoveredField.getFieldName().toString()); + Assert.assertEquals(child1, child1RecoveredField); + Assert.assertEquals("root.child1", child1RecoveredField.getFieldName().toString()); + Assert.assertEquals(child2, child2RecoveredField); + Assert.assertEquals("root.child1.child2", child2RecoveredField.getFieldName().toString()); + } + + @Test + public void getFormFieldWithNormalNamesRootIsEmpty() { + //ARRANGE + PdfFormField root = addDefaultTextFormField(outputDoc, ""); + PdfFormField child1 = addDefaultTextFormField(outputDoc, "child1"); + PdfFormField child2 = addDefaultTextFormField(outputDoc, "child2"); + child1.addKid(child2); + root.addKid(child1); + acroForm.addField(root); + + //ACT + PdfFormField rootRecoveredField = acroForm.getField(""); + PdfFormField child1RecoveredField = acroForm.getField(".child1"); + PdfFormField child2RecoveredField = acroForm.getField(".child1.child2"); + //ASSERT + Assert.assertEquals(root, rootRecoveredField); + Assert.assertEquals("", rootRecoveredField.getFieldName().toString()); + Assert.assertEquals(child1, child1RecoveredField); + Assert.assertEquals(".child1", child1RecoveredField.getFieldName().toString()); + Assert.assertEquals(child2, child2RecoveredField); + Assert.assertEquals(".child1.child2", child2RecoveredField.getFieldName().toString()); + } + + @Test + public void getFormFieldWithWhiteSpaceInNames() { + //ARRANGE + PdfFormField root = addDefaultTextFormField(outputDoc, "ro\tot"); + PdfFormField child1 = addDefaultTextFormField(outputDoc, "child 1"); + root.addKid(child1); + acroForm.addField(root); + + //ACT + PdfFormField rootRecoveredField = acroForm.getField("ro\tot"); + PdfFormField child1RecoveredField = acroForm.getField("ro\tot.child 1"); + //ASSERT + Assert.assertEquals(root, rootRecoveredField); + Assert.assertEquals("ro\tot", rootRecoveredField.getFieldName().toString()); + Assert.assertEquals(child1, child1RecoveredField); + Assert.assertEquals("ro\tot.child 1", child1RecoveredField.getFieldName().toString()); + } + + @Test + public void getFormFieldWithEmptyStringAsName() { + //ARRANGE + PdfFormField root = addDefaultTextFormField(outputDoc, "root"); + PdfFormField child1 = addDefaultTextFormField(outputDoc, ""); + PdfFormField child2 = addDefaultTextFormField(outputDoc, "child2"); + child1.addKid(child2); + root.addKid(child1); + acroForm.addField(root); + + //ACT + PdfFormField rootRecoveredField = acroForm.getField("root"); + PdfFormField child1RecoveredField = acroForm.getField("root."); + PdfFormField child2RecoveredField = acroForm.getField("root..child2"); + //ASSERT + Assert.assertEquals(root, rootRecoveredField); + Assert.assertEquals("root", rootRecoveredField.getFieldName().toString()); + Assert.assertEquals(child1, child1RecoveredField); + Assert.assertEquals("root.", child1RecoveredField.getFieldName().toString()); + Assert.assertEquals(child2, child2RecoveredField); + Assert.assertEquals("root..child2", child2RecoveredField.getFieldName().toString()); + + } + + @Test + public void getFormFieldWithTwoEmptyStringAsName() { + //ARRANGE + PdfFormField root = addDefaultTextFormField(outputDoc, "root"); + PdfFormField child1 = addDefaultTextFormField(outputDoc, ""); + PdfFormField child2 = addDefaultTextFormField(outputDoc, ""); + child1.addKid(child2); + root.addKid(child1); + acroForm.addField(root); + + //ACT + PdfFormField rootRecoveredField = acroForm.getField("root"); + PdfFormField child1RecoveredField = acroForm.getField("root."); + PdfFormField child2RecoveredField = acroForm.getField("root.."); + //ASSERT + Assert.assertEquals(root, rootRecoveredField); + Assert.assertEquals("root", rootRecoveredField.getFieldName().toString()); + Assert.assertEquals(child1, child1RecoveredField); + Assert.assertEquals("root.", child1RecoveredField.getFieldName().toString()); + Assert.assertEquals(child2, child2RecoveredField); + Assert.assertEquals("root..", child2RecoveredField.getFieldName().toString()); + } + + @Test + public void getFormFieldWithTwoEmptyStringAsNameFollowedByActualName() { + //ARRANGE + PdfFormField root = addDefaultTextFormField(outputDoc, "root"); + PdfFormField child1 = addDefaultTextFormField(outputDoc, ""); + PdfFormField child2 = addDefaultTextFormField(outputDoc, ""); + PdfFormField child3 = addDefaultTextFormField(outputDoc, "child3"); + child2.addKid(child3); + child1.addKid(child2); + root.addKid(child1); + acroForm.addField(root); + + //ACT + PdfFormField rootRecoveredField = acroForm.getField("root"); + PdfFormField child1RecoveredField = acroForm.getField("root."); + PdfFormField child2RecoveredField = acroForm.getField("root.."); + PdfFormField child3RecoveredField = acroForm.getField("root...child3"); + //ASSERT + Assert.assertEquals(root, rootRecoveredField); + Assert.assertEquals("root", rootRecoveredField.getFieldName().toString()); + Assert.assertEquals(child1, child1RecoveredField); + Assert.assertEquals("root.", child1RecoveredField.getFieldName().toString()); + Assert.assertEquals(child2, child2RecoveredField); + Assert.assertEquals("root..", child2RecoveredField.getFieldName().toString()); + Assert.assertEquals(child3, child3RecoveredField); + Assert.assertEquals("root...child3", child3RecoveredField.getFieldName().toString()); + + } + + @Test + public void getFormFieldWithAlternatingFilledInStartWithRootFilledIn() { + //ARRANGE + PdfFormField root = addDefaultTextFormField(outputDoc, "root"); + PdfFormField child1 = addDefaultTextFormField(outputDoc, ""); + PdfFormField child2 = addDefaultTextFormField(outputDoc, "child2"); + PdfFormField child3 = addDefaultTextFormField(outputDoc, ""); + + child2.addKid(child3); + child1.addKid(child2); + root.addKid(child1); + acroForm.addField(root); + + //ACT + PdfFormField rootRecoveredField = acroForm.getField("root"); + PdfFormField child1RecoveredField = acroForm.getField("root."); + PdfFormField child2RecoveredField = acroForm.getField("root..child2"); + PdfFormField child3RecoveredField = acroForm.getField("root..child2."); + //ASSERT + Assert.assertEquals(root, rootRecoveredField); + Assert.assertEquals("root", rootRecoveredField.getFieldName().toString()); + Assert.assertEquals(child1, child1RecoveredField); + Assert.assertEquals("root.", child1RecoveredField.getFieldName().toString()); + Assert.assertEquals(child2, child2RecoveredField); + Assert.assertEquals("root..child2", child2RecoveredField.getFieldName().toString()); + Assert.assertEquals(child3, child3RecoveredField); + Assert.assertEquals("root..child2.", child3RecoveredField.getFieldName().toString()); + } + + @Test + public void getFormFieldWithAlternatingFilledInStartWithRootEmpty() { + //ARRANGE + PdfFormField root = addDefaultTextFormField(outputDoc, ""); + PdfFormField child1 = addDefaultTextFormField(outputDoc, "child1"); + PdfFormField child2 = addDefaultTextFormField(outputDoc, ""); + PdfFormField child3 = addDefaultTextFormField(outputDoc, "child3"); + + child2.addKid(child3); + child1.addKid(child2); + root.addKid(child1); + acroForm.addField(root); + + //ACT + PdfFormField rootRecoveredField = acroForm.getField(""); + PdfFormField child1RecoveredField = acroForm.getField(".child1"); + PdfFormField child2RecoveredField = acroForm.getField(".child1."); + PdfFormField child3RecoveredField = acroForm.getField(".child1..child3"); + //ASSERT + Assert.assertEquals(root, rootRecoveredField); + Assert.assertEquals("", rootRecoveredField.getFieldName().toString()); + + Assert.assertEquals(child1, child1RecoveredField); + Assert.assertEquals(".child1", child1RecoveredField.getFieldName().toString()); + Assert.assertEquals(child2, child2RecoveredField); + Assert.assertEquals(".child1.", child2RecoveredField.getFieldName().toString()); + Assert.assertEquals(child3, child3RecoveredField); + Assert.assertEquals(".child1..child3", child3RecoveredField.getFieldName().toString()); + } + + + @Test + public void removeFieldWithEmptyNameCorrectlyRemoved() { + //ARRANGE + PdfFormField root = addDefaultTextFormField(outputDoc, "root"); + PdfFormField child1 = addDefaultTextFormField(outputDoc, ""); + PdfFormField child2 = addDefaultTextFormField(outputDoc, "child2"); + + root.addKid(child2); + root.addKid(child1); + acroForm.addField(root); + + //ACT + acroForm.removeField("root."); + + //ASSERT + PdfFormField rootRecoveredField = acroForm.getField("root"); + Assert.assertFalse(rootRecoveredField.getChildFields().contains(child1)); + Assert.assertTrue(rootRecoveredField.getChildFields().contains(child2)); + } + + @Test + public void removeFieldWithEmptyName2DeepCorrectlyRemoved() { + //ARRANGE + PdfFormField root = addDefaultTextFormField(outputDoc, "root"); + PdfFormField child1 = addDefaultTextFormField(outputDoc, "child1"); + PdfFormField child2 = addDefaultTextFormField(outputDoc, ""); + + child1.addKid(child2); + root.addKid(child1); + acroForm.addField(root); + + //ACT + root.removeChild(child2); + + acroForm.removeField("root.child1."); + + //ASSERT + PdfFormField rootRecoveredField = acroForm.getField("root"); + Assert.assertTrue(rootRecoveredField.getChildFields().contains(child1)); + Assert.assertFalse(rootRecoveredField.getChildFields().contains(child2)); + } + + @Test + public void removeFieldWith2EmptyNamesCorrectlyRemoved() { + //ARRANGE + PdfFormField root = addDefaultTextFormField(outputDoc, "root"); + PdfFormField child1 = addDefaultTextFormField(outputDoc, ""); + PdfFormField child2 = addDefaultTextFormField(outputDoc, ""); + + child1.addKid(child2); + root.addKid(child1); + acroForm.addField(root); + + //ACT + root.removeChild(child2); + + acroForm.removeField("root.."); + + //ASSERT + PdfFormField rootRecoveredField = acroForm.getField("root"); + Assert.assertTrue(rootRecoveredField.getChildFields().contains(child1)); + Assert.assertFalse(rootRecoveredField.getChildFields().contains(child2)); + } + + @Test + public void getFormFieldWithAllEmptyNamesCorrectlyRemoved() { + //ARRANGE + PdfFormField root = addDefaultTextFormField(outputDoc, ""); + PdfFormField child1 = addDefaultTextFormField(outputDoc, ""); + PdfFormField child2 = addDefaultTextFormField(outputDoc, ""); + + child1.addKid(child2); + root.addKid(child1); + acroForm.addField(root); + + //ACT + root.removeChild(child2); + + int sizeBeforeRemoval = acroForm.getAllFormFields().size(); + acroForm.removeField(".."); + + //ASSERT + PdfFormField rootRecoveredField = acroForm.getField(""); + Assert.assertTrue(rootRecoveredField.getChildFields().contains(child1)); + Assert.assertFalse(rootRecoveredField.getChildFields().contains(child2)); + Assert.assertEquals(sizeBeforeRemoval - 1, acroForm.getAllFormFields().size()); + + } + + @Test + public void removeFormFieldRemoveEmptyRootNoFieldsAnyMore() { + //ARRANGE + PdfFormField root = addDefaultTextFormField(outputDoc, ""); + PdfFormField child1 = addDefaultTextFormField(outputDoc, ""); + PdfFormField child2 = addDefaultTextFormField(outputDoc, ""); + + child1.addKid(child2); + root.addKid(child1); + acroForm.addField(root); + + //ACT + root.removeChild(child2); + + acroForm.removeField(""); + + //ASSERT + Assert.assertEquals(0, acroForm.getAllFormFields().size()); + } + + + @Test + public void renameFieldToEmptyNamesGetsRenamed() { + //ARRANGE + PdfFormField root = addDefaultTextFormField(outputDoc, "root"); + PdfFormField child1 = addDefaultTextFormField(outputDoc, "child1"); + root.addKid(child1); + acroForm.addField(root); + //ACT + acroForm.renameField("root.child1", ""); + //ASSERT + Assert.assertEquals("root.", child1.getFieldName().toUnicodeString()); + } + + @Test + public void renameFieldWithEmptyNameGetsRenamed() { + //ARRANGE + PdfFormField root = addDefaultTextFormField(outputDoc, "root"); + PdfFormField child1 = addDefaultTextFormField(outputDoc, "newName"); + root.addKid(child1); + acroForm.addField(root); + //ACT + acroForm.renameField("root", ""); + acroForm.renameField(".newName", ""); + //ASSERT + Assert.assertEquals(".", child1.getFieldName().toUnicodeString()); + } + + + @Test + @LogMessages(messages = { + @LogMessage(messageTemplate = + FormsLogMessageConstants.FIELDNAME_NOT_FOUND_OPERATION_CAN_NOT_BE_COMPLETED, count = 1) + }) + public void renameFieldWithNotFoundNameLogsWarning() { + //ARRANGE + PdfFormField root = addDefaultTextFormField(outputDoc, "root"); + PdfFormField child1 = addDefaultTextFormField(outputDoc, "newName"); + root.addKid(child1); + acroForm.addField(root); + //ACT + acroForm.renameField("root.", ""); + Assert.assertEquals("root.newName", child1.getFieldName().toUnicodeString()); + } + + @Test + @LogMessages(messages = { + @LogMessage(messageTemplate = + FormsLogMessageConstants.FIELDNAME_NOT_FOUND_OPERATION_CAN_NOT_BE_COMPLETED, count = 1) + }) + public void renameFieldWithNotFoundNameLogsError() { + //ARRANGE + PdfFormField root = addDefaultTextFormField(outputDoc, "root"); + PdfFormField child1 = addDefaultTextFormField(outputDoc, "newName"); + root.addKid(child1); + acroForm.addField(root); + //ACT + acroForm.renameField("root", ""); + acroForm.renameField("root.newName", ""); + //ASSERT + Assert.assertEquals(".newName", child1.getFieldName().toUnicodeString()); + } + + @Test + public void renameFieldWithEmptyName2DeepGetRenamed() { + //ARRANGE + PdfFormField root = addDefaultTextFormField(outputDoc, "root"); + PdfFormField child1 = addDefaultTextFormField(outputDoc, ""); + PdfFormField child2 = addDefaultTextFormField(outputDoc, ""); + child1.addKid(child2); + root.addKid(child1); + acroForm.addField(root); + //ACT + acroForm.renameField("root..", "newName"); + //ASSERT + Assert.assertEquals("root..newName", child2.getFieldName().toUnicodeString()); + } + + @Test + public void renameFieldWithEmptyNameRootDeepGetRenamed() { + //ARRANGE + PdfFormField root = addDefaultTextFormField(outputDoc, ""); + PdfFormField child1 = addDefaultTextFormField(outputDoc, ""); + PdfFormField child2 = addDefaultTextFormField(outputDoc, ""); + child1.addKid(child2); + root.addKid(child1); + acroForm.addField(root); + //ACT + acroForm.renameField("..", "newName"); + //ASSERT + Assert.assertEquals("..newName", child2.getFieldName().toUnicodeString()); + } + + @Test + public void renameFieldRenameAllFromEmpty() { + //ARRANGE + PdfFormField root = addDefaultTextFormField(outputDoc, ""); + PdfFormField child1 = addDefaultTextFormField(outputDoc, ""); + PdfFormField child2 = addDefaultTextFormField(outputDoc, ""); + child1.addKid(child2); + root.addKid(child1); + acroForm.addField(root); + //ACT + acroForm.renameField("", "newName"); + Assert.assertNull(acroForm.getField("")); + Assert.assertNotNull(acroForm.getField("newName")); + + acroForm.renameField("newName.", "newName"); + Assert.assertEquals("newName.newName", child1.getFieldName().toUnicodeString()); + //ASSERT + acroForm.renameField("newName.newName.", "newName"); + Assert.assertEquals("newName.newName.newName", child2.getFieldName().toUnicodeString()); + } + + @Test + public void renameMultipleTimesInLoop() { + //ARRANGE + PdfFormField root = addDefaultTextFormField(outputDoc, "root"); + PdfFormField child1 = addDefaultTextFormField(outputDoc, "-1"); + root.addKid(child1); + acroForm.addField(root); + //ACT + for (int i = 0; i < 100; i++) { + acroForm.renameField("root." + (i - 1), "" + i); + Assert.assertEquals("root." + i, child1.getFieldName().toUnicodeString()); + } + } + + + @Test + public void copyFieldWithEmptyNamesWork() { + PdfFormField root = addDefaultTextFormField(outputDoc, "root"); + PdfFormField child1 = addDefaultTextFormField(outputDoc, ""); + root.addKid(child1); + acroForm.addField(root); + PdfFormField copy = acroForm.copyField("root."); + Assert.assertNotNull( copy); + Assert.assertEquals("root.", copy.getFieldName().toUnicodeString()); + } + + @Test + public void copyFieldWithEmptyNames2DeepWork() { + PdfFormField root = addDefaultTextFormField(outputDoc, "root"); + PdfFormField child1 = addDefaultTextFormField(outputDoc, ""); + PdfFormField child2 = addDefaultTextFormField(outputDoc, ""); + child1.addKid(child2); + root.addKid(child1); + acroForm.addField(root); + PdfFormField copy = acroForm.copyField("root.."); + Assert.assertNotNull(copy); + } + + + @Test + public void replaceFieldReplacesItInTheChild() { + PdfFormField root = addDefaultTextFormField(outputDoc, "root"); + PdfFormField child1 = addDefaultTextFormField(outputDoc, "child"); + root.addKid(child1); + acroForm.addField(root); + + PdfFormField toReplace = addDefaultTextFormField(outputDoc, "toReplace"); + acroForm.replaceField("root.child", toReplace); + Assert.assertNull(acroForm.getField("toReplace")); + Assert.assertNotNull(acroForm.getField("root.toReplace")); + } + + @Test + public void replaceFieldReplacesItInTheChildWithChildNameEmpty() { + PdfFormField root = addDefaultTextFormField(outputDoc, "root"); + PdfFormField child1 = addDefaultTextFormField(outputDoc, ""); + root.addKid(child1); + acroForm.addField(root); + + PdfFormField toReplace = addDefaultTextFormField(outputDoc, "toReplace"); + acroForm.replaceField("root.", toReplace); + Assert.assertNull(acroForm.getField("toReplace")); + Assert.assertNotNull(acroForm.getField("root.toReplace")); + } + + @Test + public void copyFormFieldWithoutName() throws IOException { + + ByteArrayOutputStream f = new ByteArrayOutputStream(); + PdfDocument originalDoc = new PdfDocument(new PdfWriter(f)); + originalDoc.addNewPage(); + PdfAcroForm acroForm = PdfAcroForm.getAcroForm(outputDoc, true); + + PdfFormField root = addDefaultTextFormField(outputDoc, "root"); + PdfFormField child1 = addDefaultTextFormField(outputDoc, ""); + root.addKid(child1); + acroForm.addField(root); + + originalDoc.close(); + + InputStream i = new ByteArrayInputStream(f.toByteArray()); + PdfDocument loaded = new PdfDocument(new PdfReader(i)); + + try (PdfDocument newDoc = new PdfDocument(new PdfWriter(new ByteArrayOutputStream()))) { + PdfPageFormCopier pdfPageFormCopier = new PdfPageFormCopier(); + loaded.copyPagesTo(1, 1, newDoc, pdfPageFormCopier); + PdfAcroForm form = PdfAcroForm.getAcroForm(outputDoc, false); + Assert.assertNotNull(form); + Assert.assertEquals(2, form.getAllFormFields().size()); + Assert.assertNotNull(form.getField("root.")); + Assert.assertNotNull(form.getField("root")); + } + } + + + @Test + public void copyFormFieldWithoutRootName() throws IOException { + ByteArrayOutputStream f = new ByteArrayOutputStream(); + PdfDocument originalDoc = new PdfDocument(new PdfWriter(f)); + originalDoc.addNewPage(); + PdfAcroForm acroForm = PdfAcroForm.getAcroForm(outputDoc, true); + + PdfFormField root = addDefaultTextFormField(outputDoc, ""); + PdfFormField child1 = addDefaultTextFormField(outputDoc, ""); + + root.addKid(child1); + acroForm.addField(root); + + originalDoc.close(); + + InputStream i = new ByteArrayInputStream(f.toByteArray()); + PdfDocument loaded = new PdfDocument(new PdfReader(i)); + + try (PdfDocument newDoc = new PdfDocument(new PdfWriter(new ByteArrayOutputStream()))) { + PdfPageFormCopier pdfPageFormCopier = new PdfPageFormCopier(); + loaded.copyPagesTo(1, 1, newDoc, pdfPageFormCopier); + PdfAcroForm form = PdfAcroForm.getAcroForm(outputDoc, false); + Assert.assertNotNull(form); + Assert.assertEquals(2, form.getAllFormFields().size()); + Assert.assertNotNull(form.getField(".")); + Assert.assertNotNull(form.getField("")); + } + } + + @Test + public void copyFormFieldWithoutNameAdded2timesOverwritesTheFirst() throws IOException { + + ByteArrayOutputStream f = new ByteArrayOutputStream(); + PdfDocument originalDoc = new PdfDocument(new PdfWriter(f)); + originalDoc.addNewPage(); + PdfAcroForm acroForm = PdfAcroForm.getAcroForm(outputDoc, true); + + PdfFormField root = addDefaultTextFormField(outputDoc, "root"); + PdfFormField child1 = addDefaultTextFormField(outputDoc, ""); + + PdfFormField child2 = addDefaultTextFormField(outputDoc, ""); + + root.addKid(child2); + root.addKid(child1); + acroForm.addField(root); + + originalDoc.close(); + + InputStream i = new ByteArrayInputStream(f.toByteArray()); + PdfDocument loaded = new PdfDocument(new PdfReader(i)); + + try (PdfDocument newDoc = new PdfDocument(new PdfWriter(new ByteArrayOutputStream()))) { + PdfPageFormCopier pdfPageFormCopier = new PdfPageFormCopier(); + loaded.copyPagesTo(1, 1, newDoc, pdfPageFormCopier); + PdfAcroForm form = PdfAcroForm.getAcroForm(outputDoc, false); + Assert.assertNotNull(form); + Assert.assertEquals(2, form.getAllFormFields().size()); + Assert.assertNotNull(form.getField("root.")); + Assert.assertNotNull(form.getField("root")); + } + } + + @Test + public void addingSiblingsSameNameMergesFieldsTogether() { + PdfFormField root = addDefaultTextFormField(outputDoc, "root"); + PdfFormField child1 = addDefaultTextFormField(outputDoc, "child1"); + PdfFormField child2 = addDefaultTextFormField(outputDoc, "child1"); + root.addKid(child1); + root.addKid(child2); + acroForm.addField(root); + Assert.assertEquals(2, root.getChildFields().size()); + } + + @Test + public void addingSiblingsSameEmptyNamesMergesFieldsTogether() { + PdfFormField root = addDefaultTextFormField(outputDoc, "root"); + PdfFormField child1 = addDefaultTextFormField(outputDoc, ""); + PdfFormField child2 = addDefaultTextFormField(outputDoc, ""); + root.addKid(child1); + root.addKid(child2); + acroForm.addField(root); + Assert.assertEquals(2, root.getChildFields().size()); + } + + private static PdfFormField addDefaultTextFormField(PdfDocument doc, String name) { + return new TextFormFieldBuilder(doc, name) + .setWidgetRectangle(new Rectangle(100, 100, 100, 100)).createText(); + } +} diff --git a/forms/src/test/resources/com/itextpdf/forms/PdfFormCopyTest/cmp_copyFields07.pdf b/forms/src/test/resources/com/itextpdf/forms/PdfFormCopyTest/cmp_copyFields07.pdf index eb71d354147cefb1845fb89ecdba7f46315c37a2..99d13c3a496f9edb9f629b1fd9d12c12dc374ee0 100644 GIT binary patch delta 5327 zcmbVPZ-`u173a=0yW4HDHnYiQCQUZ8-JSiDZIXNLzjv>cfU#XLw$}V9lq%HiW`!2uJ^ZoaGEAG;p&ooVIbKVbdK<)kv8R^4*Ede=Q9t8HY?MuGwsPut8KiDG{nONNw|JX$Gb~!%)C4gUQOW30?R9@6UuLz5%Lslyx<6w|B-hL@1`GX0LQN=;NI}yus{1 z)jr0%?&D(7phT73$7^;#bY=F-q7YMw8nmd|Ux==|s19}pwK3K5m9#ATl`IX2pP+PNYS;Zw9J*)r$o%9zpJfym zSz96f%jZ8nX6ILC`rETv(Dap+ZRK0@Q7Tya?%P|-_787wAl!a)Wpg_9+y9hqZ4YRb zPG8+p?iX!7b-M5O!(}^ibz}OSYn4?L$&)F$hXJ?uRiw;GB@!Bne@~A!&qHO$z}a;J(Gc^{%rG~`s1rI&N178 zj3$ZO{bQn=n){pwtHKvvC^LBK1v2dmt|&9q7j;1<46sZ>G>fyBZMWZd)JmO<-DQb9w5Im|4p?t#@kwp@dBn(Yl<#|Mw)I>X#Ohk-GGzpQVjMF3&knWl1N#lU= z^R*a?qW-oS$e4-scubIz3|#3n0w)mdQE3@*kpPXdkwBcG#3%8o)5t}FMHYsaC>51m zVj$MT_6=<{aOplq*+J3vI+e~8ivVC<2BT%8XOfN zhEYPgzoLUpI*LvCesjZ%WVEIzc0gdOs9=eEcoBnBNxZ1R&Ax2+=tq zTObPIT2+JddUejAFy1heQ4=esbrdwG5tD|43Y*FB;sEUP;TWuW_p&GL{v}c=X`WGh zWQh!x4OYW5OJwX80bIGEvt)FQ5e$V3%VhK}gOebvE+b@A=|&*v$TE6(@+?_@0Ox*~ zS%WV>+Kf{E`d5(Q^o-6egKAM=2}QDzXZQXKq;dd{>j*tTwj*>LlYz|OsEyI_R?2j0P3v4;wxvMspQKrX|(xjdOl1`U`b|0D~A2FtBJ;8k{c?u))T1>O~xj zMnn_Z;A{yu4}iTBMqqSwPcz1s^Uq%-cRq-2Sy;L#<5B4XV2nj<)RW<1jn-?gRlny z-#wz_0>g`C7$C!HxGD4gDH^M&4RCWwaA2YyUUP36XSSUB*D?QDZ8gBFcOV1G@%^Z% zhq9)K8XiwX|GL%*h>9w_gP=_N_s32>%t$mW@Q9~jaiXmC4%*_sBkJftK}8W>t5Bw* z;Q|8U07cZGKqZFV5%jAk&XbXndt;dlm85YWE|W%C;ki0~0Ut0N_o#p13fvk0kl@nw z3pnKgXRaH%h$4Y&`~8Dv+Wp`nt}!sm9lb<0@0rtw4$s0oz8=j9Ji^r+#al*VIG;ak jKqhMU-TzC&UB5)Om1N>RxkR?#CK02*(YW`4ng0I*Iw7!B delta 5236 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 O$jXrN@#&iBk^TqlM|I)= delta 4911 zcma))({_| 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 diff --git a/forms/src/test/resources/com/itextpdf/forms/PdfFormFieldTest/cmp_maxLenDeepInheritanceTest.pdf b/forms/src/test/resources/com/itextpdf/forms/PdfFormFieldTest/cmp_maxLenDeepInheritanceTest.pdf index 8c17aab6a83685c527efb49bea3cc0d81634e0f4..2a078731f2791d4d119f2f63dfecfb21cd3d7d9c 100644 GIT binary patch delta 801 zcmezDf8KwB5|gpzWJXS#jR^}l7!5Wva-QMfG&V3aFf=wbFxY%e5GbNxs9*quMd_0} zg=HtN6ZT+KnEXvxlRYH0q67#wtBG_mZQdcajtgYeN+laci^=&Sk@ZtA81gk6@Gu;> zyZ*xsJvBu`&oHf9E1B2M4_I-9k@HI9^XoGMmKq40lzX&UHkHk}vC#XpG>?=|pOo9{ z6Y4Q+5}#E~e`b6v)SjvSCE#S&+rLc4K2mEPO1DY`x_t;S&hpaG-!S8M=~d5jpBnwP ze4T&W;iCAT4Ugxl#?RXSuHw9iY|g>@avOD{mks=;KMM}HFN>)9|E&7Om*o0yxluDF z8*&IU8VnxSTHtjpS885LaY<2XVlIf4pOnQ_QIwj-WuRbUIQgUUrFtU;0}xQiQ{VzK z3``BpfsuwLW@2EDA!cfXsn^^TQ=NqghIwX&h87U>_z)JG85x*Q4p3FIGdD%I%goH! z3`5M^2xc=r&kbm>W!XQMXYr zPBu%@me>W}w D|0E28 delta 820 zcmX@_|Ji?n5|fGHWJXS#jR^}l7>zbFa-QMfG%~O-v@|v_u-tr2a5JN_roLN*fkKdiu?dKZ)elerGbXPU_L}@dSd%?G-#OQ6kO2?FgX(!o?t4>S&iWUtetGhVEf2U~u*pq%yQn|Ed?QyM z+a>2us;xZ-p71iJ9eO4z+h;#tTSTmVw@4n_a~