Page 1 of 1

Locate field in document

PostPosted: Thu Dec 16, 2021 3:02 am
by kaldvelski
Hi there,

I'm trying to insert an image into a document using a FieldRef as the locator.

So far I've been able to find the field in question using ComplexFieldLocator and have successfully inserted an image at the end of the document by simply adding to the MainDocumentPart but I can't marry the two together.

The FieldRef object I'm able to locate does not seem to have a reference to the main document part. The result of calling getParent() is a P but it is not the same P instance as when I look at the P instance from getMainDocumentPart().getContent().
So any attempt to add a Drawing or Run to the P associated with the FieldRef is not inserting the image into the document - I assume because it's a clone or something and thus doesn't affect the MainDocumentPart.

Is there a way to traverse from a FieldRef to the MainDocumentPart so that I can insert an image at my desired location?

Re: Locate field in document

PostPosted: Thu Dec 16, 2021 5:29 pm
by jason
The P should be the right P... perhaps you could attach a sample docx and the code you are using around ComplexFieldLocator

Re: Locate field in document

PostPosted: Thu Dec 16, 2021 7:52 pm
by kaldvelski
Hi Jason, here's the code and document:

Code: Select all
void test() {

    String template = "pictureTemplate.docx";
    try (FileInputStream fis = new FileInputStream(template)) {
        final WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(fis);

        List<FieldRef> fieldRefs = findAllFieldRefs(wordMLPackage);
     
        FieldRef field = findMergeField(wordMLPackage, fieldRefs, "Image:client.picture");
        byte[] bytes = loadImageFromFile(); 
        Inline inline = newImage(wordMLPackage, bytes);
        org.docx4j.wml.ObjectFactory factory = Context.getWmlObjectFactory();
        org.docx4j.wml.Drawing drawing = factory.createDrawing();
        drawing.getAnchorOrInline().add(inline);

        org.docx4j.wml.R run = factory.createR();
        run.getContent().add(drawing);
         
        //This does not work, either adding drawing directly or via a Run
        field.getParent().getContent().add(drawing);
         
        //this works and adds image to end of document
        wordMLPackage.getMainDocumentPart().getContent().add(drawing);

      //Find P in MainDocumentPart
      for(Object o : wordMLPackage.getMainDocumentPart().getContent()) {
        if (o instanceof P) {
          P mainP = (P) o;
          if (TextUtils.getText(mainP).contains("Image:client.picture")) {
            //Check if the field P matches that in the Main document - DOES NOT
            System.out.println("Field P = MainDocumentP: " + (((P) field.getParent()) == mainP));
           
            //This will add the image at the correct location
            mainP.getContent().add(drawing);
            break;
          }
        }
      }
       

      } catch (IOException | Docx4JException e) {
        e.printStackTrace();
      }
  }

private FieldRef findMergeField(final WordprocessingMLPackage wordMLPackage, List<FieldRef> fieldRefs, final String fieldDescription) {
    for (FieldRef fr : fieldRefs) {
      for(Object o : fr.getInstructions() ) {
        final Object obj = XmlUtils.unwrap(o);
        if (obj instanceof Text) {
          String instr = ((Text) obj).getValue();
          if (instr.contains("MERGE") && instr.contains(fieldDescription)) {
            return fr;
          }
        }
      }
    }
    return null;

  }

  private List<FieldRef> findAllFieldRefs(final WordprocessingMLPackage wordMLPackage) {
    List<Object> contentList = wordMLPackage.getMainDocumentPart().getContent();
    ComplexFieldLocator fl = new ComplexFieldLocator();
    new TraversalUtil(contentList, fl);

    // canonicalise and setup fieldRefs
    List<FieldRef> fieldRefs = new ArrayList<>();
    for( P p : fl.getStarts() ) {
      FieldsPreprocessor.canonicalise(p, fieldRefs);
    }
    return fieldRefs;
  }

  private static void recurse(List<String> mergeFields, FieldRef fr, String indent) {

    for(Object o : fr.getInstructions() ) {
      if (o instanceof FieldRef) {
        recurse(mergeFields, ((FieldRef)o), indent + "    ");
      } else {
        o = XmlUtils.unwrap(o);
        if (o instanceof Text) {
          String instr = ((Text)o).getValue();
     if (instr.contains("MERGE")) {
            mergeFields.add(instr);
     }
        } else {
          System.out.println(indent + XmlUtils.unwrap(o).getClass().getName());
        }
      }
    }

  }

public Inline newImage( WordprocessingMLPackage wordMLPackage,
                                           byte[] bytes) throws Exception {

    BinaryPartAbstractImage imagePart = BinaryPartAbstractImage.createImagePart(wordMLPackage, bytes);

    return imagePart.createImageInline( "", "", 0, 1, false);

  }

private byte[] loadImageFromFile() {
    final File f = new File("image1.jpg");

    try (InputStream is = new java.io.FileInputStream(f)) {
      long length = f.length();
      // You cannot create an array using a long type.
      // It needs to be an int type.
      if (length > Integer.MAX_VALUE) {
        System.out.println("File too large!!");
      }
      byte[] bytes = new byte[(int) length];
      int offset = 0;
      int numRead = 0;
      while (offset < bytes.length
          && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) {
        offset += numRead;
      }
      // Ensure all the bytes have been read in
      if (offset < bytes.length) {
        System.out.println("Could not completely read file " + f.getName());
      }

      return bytes;

    } catch (IOException e) {
      e.printStackTrace();
      throw new RuntimeException("Unable to open image file");
    }

  }

Re: Locate field in document

PostPosted: Fri Dec 17, 2021 8:18 am
by jason
Your code includes:

Code: Select all
         FieldsPreprocessor.canonicalise(p, fieldRefs);

That does create new P objects, but it doesn't replace the existing ones. To do that, try:

Syntax: [ Download ] [ Hide ]
Using java Syntax Highlighting
                        int index = ((ContentAccessor)p.getParent()).getContent().indexOf(p);
                        P newP = FieldsPreprocessor.canonicalise(p, fieldRefs);
                        ((ContentAccessor)p.getParent()).getContent().set(index, newP);
Parsed in 0.015 seconds, using GeSHi 1.0.8.4


Do you have to work with fields? If you are able to alter the input documents, you'd be much better off with content control data binding.

Re: Locate field in document

PostPosted: Fri Dec 17, 2021 10:05 pm
by kaldvelski
Thanks Jason, that worked.

I need to maintain backward compatibility with MailMerge fields so need to stick with them, but also need a way to implement more complex data than just strings, e.g. lists and images.