尝试访问 XSSFShape 的父级会导致 NullPointerException

Trying to access the parent of an XSSFShape results in NullPointerException

在尝试 时,我想区分 XSSFPictures 哪些是 XSSFShapeGroup 的子代,哪些不是 XSSFShapeGroup 的子代:

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);
        }
    }
}

无论 XSSFPicture 是否是 XSSFShapeGroup 的子代,这对于每种情况都是一样的。

在我执行了这两个测试来检查它的父级后,这看起来特别奇怪。

System.out.println(Objects.isNull(container instanceof XSSFShapeGroup)); // Output: false
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
}

这清楚地表明父级确实存在,也允许我们在进程中检查父级。

我还没有检查其他类型的 XSSFShapes,例如 XSSFSimpleShapeXSSFConnector。但是,由于它们都继承了相同的 class,即 XSSFShape,我想结果不会有太大差异。

那么 XSSFShape.getParent() 可能有什么问题或者我对问题的看法不正确?

这是因为 apache poi 直到现在(2021 年 5 月,版本 apache poi 5.0.0)还不完整。它会影响形状组中的所有形状。

XSSFShape.getParent 只是 returns XSSFShape 的 class 成员 XSSFShapeGroup parent。但是在解析 Office Open XML 绘图时,apache poi 只是执行以下操作:

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

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

XSSFShapeGroup 的构造函数只是

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

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

所以这不需要遍历该组中的所有形状并将它们的 parent 设置为该形状组。所以 parent 永远是 null.

所以要么你得到 parent 使用低级别 classes,正如你已经在你的问题中显示的那样。或者,更好的是,您在方法 traverseShapeContainer(ShapeContainer<XSSFShape> container) 中检查 container,其中 所有遍历形状的 parent 容器。你可以得到这是 XSSFShapeGroup 还是 XSSFDrawing.

像这样:

...
 ... 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);

   }
  }

 }
...