1

While trying to traverse the inner contents of the ShapeContainer, I wanted to differentiate between XSSFPictures which are children of XSSFShapeGroup and which are not by checking it's parent:

private void traverseShapeContainer(ShapeContainer<XSSFShape> container) {
    for (XSSFShape shape : container) {
          // Other types of XSSFShapes have not been mentioned here.
          if (shape instanceof XSSFPicture) {
            XSSFPicture picture = (XSSFPicture) shape;
            System.out.println(shape.getParent() instanceof XSSFShapeGroup); // Always outputs false
            System.out.println(Objects.isNull(shape.getParent())); // Always outputs true
        } else if (shape instanceof XSSFShapeGroup) {
            XSSFShapeGroup shapeGroup = (XSSFShapeGroup) shape;
            // accessing inner contents of XSSFShapeGroup
            traverseShapeContainer(shapeGroup);
        }
    }
}

This was the same for every case whether an XSSFPicture was a child of a XSSFShapeGroup or not.

This seemed particularly strange after I performed these two tests to check for it's parent.

  • Test 1: Checking whether the ShapeContainer is an instance of XSSFShapeGroup or not.
System.out.println(Objects.isNull(container instanceof XSSFShapeGroup)); // Output: false
  • Test 2: Looking for the parent tag after visiting the child node.
if (shape instanceof XSSFPicture) {
   XSSFPicture picture = (XSSFPicture) shape;
   // "xdr:grpSp" is the tag for XSSFShapeGroup
   System.out.println(picture.getCTPicture().getDomNode().getParentNode().getNodeName()
                      .equals("xdr:grpSp")); // Output: true
}

This clearly shows that the parent does exist and also allows us to check for the parent in the process.

I haven't checked for other types of XSSFShapes such as XSSFSimpleShape or XSSFConnector yet. However, since they all inherit the same class,i.e, XSSFShape I guess the results wouldn't be much different.

So what might be wrong with XSSFShape.getParent() or is my perception of the problem incorrect?

dodobird
  • 168
  • 11

1 Answers1

2

This is because of incompleteness of apache poi until now (May 2021, version apache poi 5.0.0). It it affects all shapes in a shape group.

XSSFShape.getParent simply returns the class member XSSFShapeGroup parent of the XSSFShape. But while parsing the Office Open XML drawing, apache poi simply does the following:

...
 } else if (obj instanceof CTGroupShape) {
  shape = new XSSFShapeGroup(this, (CTGroupShape) obj);
 }...

See https://svn.apache.org/viewvc/poi/tags/REL_5_0_0/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFDrawing.java?view=markup#l619.

And constructor of XSSFShapeGroup simply does

protected XSSFShapeGroup(XSSFDrawing drawing, CTGroupShape ctGroup) {
 this.drawing = drawing;
 this.ctGroup = ctGroup;
}

See https://svn.apache.org/viewvc/poi/tags/REL_5_0_0/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFShapeGroup.java?view=markup#l55.

So this lacks traversing all shapes in that group and set their parent to that shape group. So parent always will be null.

So either you get the parent using the low level classes, as you have shown already in your question. Or, better, you do checking the container in your method traverseShapeContainer(ShapeContainer<XSSFShape> container), which is the parent container of all traversed shapes. You can get whether this is a XSSFShapeGroup or is the XSSFDrawing.

Like so:

...
 ... void traverseShapeContainer(ShapeContainer<XSSFShape> container) {
  for (XSSFShape shape : container) { 
   // possible:   XSSFConnector, XSSFGraphicFrame, XSSFPicture, XSSFShapeGroup, XSSFSimpleShape
   if (shape instanceof XSSFConnector) {
    XSSFConnector connector = (XSSFConnector)shape;
    System.out.println(connector);

   } else if (shape instanceof XSSFGraphicFrame) {
    XSSFGraphicFrame graphicFrame = (XSSFGraphicFrame)shape;
    System.out.println(graphicFrame);

   } else if (shape instanceof XSSFPicture) {
    XSSFPicture picture = (XSSFPicture)shape;
    System.out.println(picture);
    if (container instanceof XSSFDrawing) {
     System.out.println("Picture is in drawing directly.");
    } else if (container instanceof XSSFShapeGroup) {
     System.out.println("Picture is in shape group.");
     XSSFShapeGroup parent = (XSSFShapeGroup) container;
     System.out.println("Parent is " +  parent);
    }

   } else if (shape instanceof XSSFShapeGroup) { //we have a shape group
    XSSFShapeGroup shapeGroup = (XSSFShapeGroup)shape;
    System.out.println(shapeGroup);

    traverseShapeContainer(shapeGroup); // we now can sinply get the XSSFShapeGroup as ShapeContainer<XSSFShape>

   } else if (shape instanceof XSSFSimpleShape) {
    XSSFSimpleShape simpleShape = (XSSFSimpleShape)shape;
    System.out.println(simpleShape);

   }
  }

 }
...
Axel Richter
  • 56,077
  • 6
  • 60
  • 87