OfficeDev / office-js

A repo and NPM package for Office.js, corresponding to a copy of what gets published to the official "evergreen" Office.js CDN, at https://appsforoffice.microsoft.com/lib/1/hosted/office.js.
https://learn.microsoft.com/javascript/api/overview
Other
671 stars 96 forks source link

RichApi.Error: GeneralException when loading shapes in PowerPoint Online #4906

Open elegault opened 1 week ago

elegault commented 1 week ago

Calling ShapeCollection.load() on a collection retrieved from SlideLayout.shapes causes a RichApi.Error: GeneralException in PowerPoint Online:

{
    "stack": "RichApi.Error: GeneralException\n    at new n (https://appsforoffice.microsoft.com/lib/1/hosted/powerpoint-web-16.00.js:26:319657)\n    at i.processRequestExecutorResponseMessage (https://appsforoffice.microsoft.com/lib/1/hosted/powerpoint-web-16.00.js:26:384274)\n    at https://appsforoffice.microsoft.com/lib/1/hosted/powerpoint-web-16.00.js:26:382336",
    "message": "GeneralException",
    "name": "RichApi.Error",
    "code": "GeneralException",
    "traceMessages": [],
    "innerError": null,
    "debugInfo": {
        "code": "GeneralException",
        "message": "GeneralException",
        "errorLocation": "SlideMasterCollection.getItem",
        "statement": "var slideMaster = slideMasters.getItem(...);",
        "surroundingStatements": [
            "var root = context.root;",
            "var slideMasters = root.slideMasters;",
            "// >>>>>",
            "var slideMaster = slideMasters.getItem(...);",
            "// <<<<<",
            "var layouts = slideMaster.layouts;",
            "var layout = layouts.getItem(...);",
            "var shapes = layout.shapes;",
            "shapes.load();"
        ],
        "fullStatements": [
            "Please enable config.extendedErrorLogging to see full statements."
        ]
    },
    "httpStatusCode": 500
}

The error does not occur in PowerPoint for Windows,

Your Environment

Expected behavior

The ShapeCollection is loaded and available for enumeration.

Current behavior

The error listed above occurs.

Steps to reproduce

Load this script in Script Lab:

name: Log Slides and Masters
description: Logs metadata of the selected slides and its masters
host: POWERPOINT
api_set: {}
script:
  content: |
    $("#run").on("click", getSelectedSlideIndices);

    async function run() {
      await PowerPoint.run(async (context) => {
        //Get the selected slides
        const selectedSlides = await context.presentation.getSelectedSlides();

        selectedSlides.load("items/index");
        await context.sync();

        console.log(`${selectedSlides.items.length} selected slides`);
        console.dir(selectedSlides);

        //Loop through the selected slides and log the index and id of each slide
        for (let i = 0; i < selectedSlides.items.length; i++) {
          console.log(
            `Selected slide (${i + 1} of ${selectedSlides.items.length}; index ${i}) id: ${selectedSlides.items[i].id}`
          );
        }

        const allSlides = context.presentation.slides.load("slides");
        await context.sync();

        console.log(`allSlides.items.length: ${allSlides.items.length}`);
        console.dir(allSlides);

        for (let i = 0; i < allSlides.items.length; i++) {
          console.log(`Slide ${i + 1} id: ${allSlides.items[i].id}`);
        }
      });
    }

    async function getSelectedSlideIndices() {
      Office.context.document.getSelectedDataAsync(Office.CoercionType.SlideRange, getSelectedSlideIndicesCallBack);
    }

    async function getSelectedSlideIndicesCallBack(asyncResult) {
      if (asyncResult.status === Office.AsyncResultStatus.Failed) {
        console.error(asyncResult.error.message);
      } else {
        console.log(JSON.stringify(asyncResult.value, null, 4));

        const selectedSlideIds = {};

        //Loop through the asyncResult.value.slides array and store the id of each slide in the allSlidesList object
        asyncResult.value.slides.map((slide, index) => {
          selectedSlideIds[index] = slide.index;
        });

        //Loop through the asyncResult.value.slides array and create an array of objects with the id and index properties
        const selectedSlides = asyncResult.value.slides.map((slide, index) => {
          return { id: slide.id, index: slide.index };
        });

        await processSelection(selectedSlides);
      }

      async function processSelection(slideIndices) {
        await PowerPoint.run(async function(context) {
          console.log("Selected slide indices:");
          console.log(`Processing ${slideIndices.length} selected slides...`);

          //Loop through slideIndices object
          for (let i = 0; i < slideIndices.length; i++) {
            context.presentation.slides.load("slides");
            await context.sync();

            const slide = context.presentation.slides.getItemAt(slideIndices[i].index - 1);
            slide.load("id, layout, slideMaster");
            await context.sync();

            //Get the shapes in the layout
            const shapes = slide.shapes;
            shapes.load("type");
            await context.sync();

            console.log(
              `shapes.items.length: ${shapes.items.length}; slide index: ${slideIndices[i].index}; id: ${slide.id}`
            );

            for (let j = 0; j < shapes.items.length; j++) {
              shapes.items[j].load("name, id, textFrame");
              await context.sync();

              const textFrame = shapes.items[j].textFrame;
              const textRange = textFrame.textRange;

              textRange.load("text");
              await context.sync();

              console.log(
                `Shape ID: ${shapes.items[j].id}; Shape name: ${shapes.items[j].name}; Shape text: '${textRange.text}'`
              );
            }

            //Log the slide master's metadata
            const slideMaster = slide.slideMaster;

            slideMaster.load("id, name, layouts/items/name, layouts/items/id");
            await context.sync();

            // Loop through all slide masters to find the one that matches the current slide. We have to do this because of bugs in the API if we process collections directly from the slideMaster object from a slide
            const slideMasters = context.presentation.slideMasters.load("id, name, layouts/items/name, layouts/items/id");
            await context.sync();

            for (let i = 0; i < slideMasters.items.length; i++) {
              if (slideMasters.items[i].id !== slideMaster.id) {
                continue;
              }

              console.log(`Master name: ${slideMasters.items[i].name} (id ${slideMasters.items[i].id})`);

              // Log the name and ID of each slide layout in the slide master.
              const layoutsInMaster = slideMasters.items[i].layouts;

              // Log the count of layouts
              console.log(`Processing ${layoutsInMaster.items.length} layouts in master ${slideMasters.items[i].name}...`);

              for (let j = 0; j < layoutsInMaster.items.length; j++) {
                console.log("Layout name: " + layoutsInMaster.items[j].name + " Layout ID: " + layoutsInMaster.items[j].id);
                //Possible errors in PowerPoint Online!!
                try {
                  layoutsInMaster.items[j].load();
                  await context.sync();

                  // BUG 9/16/2024: Uncaught (in promise) RichApi.Error: GeneralException. But the shapes collection IS there
                  layoutsInMaster.items[j].shapes.load();
                  await context.sync();
                } catch (error) {
                  console.error("Error:", error);
                  return;
                }

                const shapes = layoutsInMaster.items[j].shapes;

                try {
                  console.log(shapes.items.length + " shape items in layout " + layoutsInMaster.items[j].name);
                } catch (error) {
                  console.error("Error:", error);
                  return;
                }

                //Loop through the shapes collection
                for (let s = 0; s < shapes.items.length; s++) {
                  console.log(`Shape name: ${shapes.items[s].name}; Shape ID: ${shapes.items[s].id}`);
                }
              }
            }
          }
        });
      }
    }
  language: typescript
template:
  content: |-
    <button id="run" class="ms-Button">
        <span class="ms-Button-label">Log Metadata for Selected Slides and their Slide Masters</span>
    </button>
  language: html
style:
  content: |-
    section.samples {
        margin-top: 20px;
    }

    section.samples .ms-Button, section.setup .ms-Button {
        display: block;
        margin-bottom: 5px;
        margin-left: 20px;
        min-width: 80px;
    }
  language: css
libraries: |
  https://appsforoffice.microsoft.com/lib/1/hosted/office.js
  @types/office-js

  office-ui-fabric-js@1.4.0/dist/css/fabric.min.css
  office-ui-fabric-js@1.4.0/dist/css/fabric.components.min.css

  core-js@2.4.1/client/core.min.js
  @types/core-js

  jquery@3.1.1
  @types/jquery@3.3.1

Provide additional details

  1. Open a PowerPoint presentation
  2. Select one or more slides
  3. Click the button in the Script Lab to run the provided script

EDIT: After some testing, it appears to be happening only with a presentation created from a custom template: PresentationFromCustomTemplate.pptx

The error does not occur with a new, blank presentation from no template: PresentationFromNoTemplate.pptx

EsterBergen commented 1 week ago

@elegault - Thanks for sharing. Do you know if this error was always happening or if this is a new error/regression?

elegault commented 1 week ago

@elegault - Thanks for sharing. Do you know if this error was always happening or if this is a new error/regression?

I've never worked with the shapes collections before, so I don't know if it ever worked in PowerPoint Online.

elegault commented 1 week ago

I have isolated the cause: if I rename the "Fußzeilenplatzhalter 7" footer in the slide master of the custom template to anything that doesn't have the "ß" character, the exception is not thrown! So my guess is that it is a character encoding issue of some kind! Not sure what other special characters can cause this.