Fill XFA without breaking usage rights

274 Views Asked by At

I have an XFA form that I can successfully fill in by extracting the XML modifying and writing back. Works great if you have the full Adobe Acrobat, but fails with Adobe Reader. I have seen various questions on the same thing with answers but they were some time ago so updating an XFA that is readable by Adobe Reader may no longer be doable? I use this code below and I've utilised the StampingProperties of append as in the iText example but still failing. I'm using iText 7.1.15.

        //open file and write to temp one
        PdfDocument pdf = new(new PdfReader(FileToProcess), new PdfWriter(NewPDF), new StampingProperties().UseAppendMode());
        PdfAcroForm form = PdfAcroForm.GetAcroForm(pdf, true);
        XfaForm xfa = form.GetXfaForm();
        XElement node = xfa.GetDatasetsNode();
        IEnumerable<XNode> list = node.Nodes();
        foreach (XNode item in list)
        {
            if (item is XElement element && "data".Equals(element.Name.LocalName))
            {
                node = element;
                break;
            }
        }

        XmlWriterSettings settings = new() { Indent = true };

        using XmlWriter writer = XmlWriter.Create(XMLOutput, settings);
        {
            node.WriteTo(writer);
            writer.Flush();
            writer.Close();
        }

        //We now how to strip an extra xfa line if updating
        if(update)
        {
            string TempXML= CSTrackerHelper.MakePath($"{AppContext.BaseDirectory}Temp", $"{Guid.NewGuid()}.XML");
            StreamReader fsin = new(XMLOutput);
            StreamWriter fsout = new(TempXML);
            string linedata = string.Empty;
            int cnt = 0;
            while (!fsin.EndOfStream)
            {
                if (cnt != 3 && linedata != string.Empty)
                {
                    fsout.WriteLine(linedata);
                }
                linedata = fsin.ReadLine();
                cnt++;
            }
            fsout.Close();
            fsin.Close();
            XMLOutput = TempXML;
        }


        xlogger.Info("Populating pdf fields");

        //Now loop through our field data and update the XML
        XmlDocument xmldoc = new();
        xmldoc.Load(XMLOutput);

        XmlNamespaceManager xmlnsManager = new(xmldoc.NameTable);
        xmlnsManager.AddNamespace("xfa", @"http://www.xfa.org/schema/xfa-data/1.0/");
        string[] FieldValues;
        string[] MultiNodes;

        foreach (KeyValuePair<string, DocumentFieldData> v in DocumentData.FieldData)
        {
            if (!string.IsNullOrEmpty(v.Value.Field))
            {
                FieldValues = v.Value.Field.Contains(";") ? v.Value.Field.Split(';') : (new string[] { v.Value.Field });
                foreach (string FValue in FieldValues)
                {
                    XmlNodeList aNodes;
                    if (FValue.Contains("{"))
                    {
                        aNodes = xmldoc.SelectNodes(FValue.Substring(0, FValue.LastIndexOf("{")), xmlnsManager);
                        if (aNodes.Count > 1)
                        {
                            //We have a multinode
                            MultiNodes = FValue.Split('{');
                            int NodeIndex = int.Parse(MultiNodes[1].Replace("}", ""));
                            aNodes[NodeIndex].InnerText = v.Value.Data;
                        }
                    }
                    else
                    {
                        aNodes = xmldoc.SelectNodes(FValue, xmlnsManager);
                        if (aNodes.Count >= 1)
                        {
                            aNodes[0].InnerText = v.Value.Data;
                        }
                    }
                }
            }
        }
        xmldoc.Save(XMLOutput);

        //Now we've updated the XML apply it to the pdf
        xfa.FillXfaForm(new FileStream(XMLOutput, FileMode.Open, FileAccess.Read));
        xfa.Write(pdf);
        pdf.Close();

FYI I've also tried to set a field directly also with the same results.

        PdfReader preader = new PdfReader(source);
        PdfDocument pdfDoc=new PdfDocument(preader, new PdfWriter(dest), new StampingProperties().UseAppendMode());
        PdfAcroForm pdfForm = PdfAcroForm.GetAcroForm(pdfDoc, true);
        XfaForm xform = pdfForm.GetXfaForm();
        xform.SetXfaFieldValue("VRM[0].CoverPage[0].Wrap2[0].Table[0].CSID[0]", "Test");
        xform.Write(pdfForm);
        pdfDoc.Close();

If anyone has any ideas it would be appreciated. Cheers

2

There are 2 best solutions below

1
Ali Serag On

I ran into a very similar issue. I was attempting to auto fill an XFA that was password protected while not breaking the certificate or usage rights (it allowed filling). iText7 seems to have made this not possible for legal/practical reasons, however it is still very much possible with iText5. I wrote the following working codeusing iTextSharp (C# version if iText5):

using iTextSharp.text;
using iTextSharp.text.pdf;

string pathToRead = "/Users/home/Desktop/c#pdfParser/encrypted_empty.pdf";
string pathToSave = "/Users/home/Desktop/c#pdfParser/xfa_encrypted_filled.pdf";
string data = "/Users/home/Desktop/c#pdfParser/sample_data.xml";


FillByItextSharp5(pathToRead, pathToSave, data);

static void FillByItextSharp5(string pathToRead, string pathToSave, string data)
{
    using (FileStream pdf = new FileStream(pathToRead, FileMode.Open))
    using (FileStream xml = new FileStream(data, FileMode.Open))
    using (FileStream filledPdf = new FileStream(pathToSave, FileMode.Create))
    {
        PdfReader.unethicalreading = true;

        PdfReader pdfReader = new PdfReader(pdf);
        PdfStamper stamper = new PdfStamper(pdfReader, filledPdf, '\0', true);
        stamper.AcroFields.Xfa.FillXfaForm(xml, true);
        stamper.Close();
        pdfReader.Close();
    }
}
0
Super W Star On

PdfStamper stamper = new PdfStamper(pdfReader, filledPdf, '\0', true) you have to use this line.