首页 > 解决方案 > 尝试访问 XSSFShape 的父级导致 NullPointerException

问题描述

在尝试遍历 ShapeContainer 的内部内容时,我想通过检查它的父级来区分XSSFPictures哪些是子级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无论an是否是a的孩子,这对于每种情况都是相同的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比如XSSFSimpleShape或者XSSFConnector还没有。但是,由于它们都继承同一个类,即,XSSFShape我想结果不会有太大不同。

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

标签: javaapache-poi

解决方案


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

XSSFShape.getParent简单地返回XSSFShapeGroup parent. XSSFShape但是在解析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永远都是null

因此,正如您在问题中已经显示的那样,您可以使用低级类来获取父级。或者,更好的是,你检查container你的方法traverseShapeContainer(ShapeContainer<XSSFShape> container),它所有遍历形状的父容器。您可以知道这是 aXSSFShapeGroup还是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);

   }
  }

 }
...

推荐阅读