Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat?] make slide masters/layouts editable. #85

Open
MP70 opened this issue Jan 21, 2024 · 5 comments
Open

[Feat?] make slide masters/layouts editable. #85

MP70 opened this issue Jan 21, 2024 · 5 comments
Labels
enhancement New feature or request refactor

Comments

@MP70
Copy link
Collaborator

MP70 commented Jan 21, 2024

I think this would be 'not too difficult' to add; we just need to work out how to present it to the user.. maybe slide.modifyMaster()?

I appreciate the same can be achieved by re adding the master pres.addMaster() that's already there but this seems really inefficient and requires user to have already looked up the slide master relationship.

This would enable us to change text or logos that are stored in the masters. For example if a user puts the customer name and logo in the master we can still automate that without requiring them to pull them out of the master.

What do you think?

Alongside this I'd also like to work on getting slide backgrounds something we can edit.

@singerla
Copy link
Owner

Hi Matt,

I really like your idea, but I'm afraid, this is more complicated than expected. :)

It's basically because a modification applies only on adding it - regardless of a shape's location (slide or slideMaster). It can't be modified in place, this would require some more refactoring.

But I could imagine a shurtcut like this:

 const pres = await automizer
    .loadRoot(`EmptyTemplate.pptx`)
    .load('SlideWithShapes.pptx')

    // modifyMaster could wrap around addMaster:
    .modifyMaster('SlideWithShapes.pptx', 1, (master) => {
      master.modifyElement(
        `MasterRectangle`,
        ModifyTextHelper.setText('my text on master'),
      );
    })

This is similar to autoImportSlideMasters: true, which also is a wrapper for underlying fetaures.
I need to go deeper into this.

@singerla singerla added enhancement New feature or request refactor labels Jan 25, 2024
@MP70
Copy link
Collaborator Author

MP70 commented Jan 25, 2024

Yes, I agree. I was thinking something that wrapped add master that 'offloaded' looking up the relationship and deduplication (in practice i suppose addMaster actually would overwrite the existing master). If we have one master applied to many slides it should ensure we are not actually copying the master each time and it remains a single master file.

Thinking about this further though there would potentially be cases where the master is on the root template, is there a way we could edit this once, without calling .addMaster on each slide manually.

@jhaase1
Copy link

jhaase1 commented Dec 24, 2024

I tried adding the masters making the modifications, then removing the originals. It does add the masters then remove the old ones; however, I am running into the repair issue. I believe the issue might be that the id for the masters might be too low if there are only modified masters in the presentation because without modification the slide master ids are 2147483660 to 2147483678, but after modification are 0 & 6.

If the default master id were increased, I think it may solve the issue?

currentId = Number(slide.getAttribute('id'));

async function removeMasters(pres, length, from) {
  const xml = await XmlHelper.getXmlFromArchive(
    pres.rootTemplate.archive,
    `ppt/presentation.xml`,
  );

  const existingMasters = xml.getElementsByTagName('p:sldMasterId');

  XmlHelper.sliceCollection(existingMasters, length, from);

  XmlHelper.writeXmlToArchive(
    await pres.rootTemplate.archive,
    `ppt/presentation.xml`,
    xml,
  );

  await pres.modify(ModifyPresentationHelper.normalizeSlideMasterIds);
}

@jhaase1
Copy link

jhaase1 commented Dec 24, 2024

Big surprise it's more complicated than that. I don't think FileHelper is the correct function to use here? possibly remove from pres.rootTemplate.archive.archive.files? Or does it need to be removed from pres.rootTemplate.archive.file also?

async function removeMasters(pres, length, from) {
  const xml = await XmlHelper.getXmlFromArchive(
    pres.rootTemplate.archive,
    `ppt/presentation.xml`,
  );

  for (let i = 0; i < length; i += 1) {
    const layouts = await new XmlRelationshipHelper().initialize(
      pres.rootTemplate.archive,
      `slideMaster${from + i + 1}.xml.rels`,
      `ppt/slideMasters/_rels`,
      '../slideLayouts/slideLayout',
    );

    const layoutFiles = layouts.map((f) =>
      f.file, // path.resolve(`ppt/presentation.xml/${f.file}`),
    );

    await FileHelper.removeFromDirectory(
      pres.rootTemplate.archive,
      '../slideLayouts/',
      (file) => {
        return !layoutFiles.includes(file.relativePath);
      },
    );

    const themes = await new XmlRelationshipHelper().initialize(
      pres.rootTemplate.archive,
      `slideMaster${from + i + 1}.xml.rels`,
      `ppt/slideMasters/_rels`,
      '../theme/theme',
    );

    const themesFiles = themes.map((f) =>
      f.file, // path.resolve(`ppt/presentation.xml/${f.file}`),
    );

    await FileHelper.removeFromDirectory(
      pres.rootTemplate.archive,
      '../theme/',
      (file) => {
        return !themesFiles.includes(file.relativePath);
      },
    );
  }

  const existingMasters = xml.getElementsByTagName('p:sldMasterId');

  XmlHelper.sliceCollection(existingMasters, length, from);

  XmlHelper.writeXmlToArchive(
    await pres.rootTemplate.archive,
    `ppt/presentation.xml`,
    xml,
  );

  // await pres.modify(ModifyPresentationHelper.normalizeSlideMasterIds);
}

@singerla
Copy link
Owner

singerla commented Dec 25, 2024

Hi @jhaase1!

Thanks a lot for your code. I've edited it a little and moved it to

static removeSlideMaster =
(length: number, from: number, pres: Automizer) =>
async (presXml: XmlDocument) => {
for (let i = 0; i < length; i += 1) {
const targetMasterId = from + i + 1;
const masterToRemove = `slideMaster${targetMasterId}.xml`;
const layouts = (await new XmlRelationshipHelper().initialize(
pres.rootTemplate.archive,
`${masterToRemove}.rels`,
`ppt/slideMasters/_rels`,

Please check out feature-remove-master branch to test and improve it.

Some thoughts on it:

  • It is really tricky to figure out a proper Id for the first master. Please also see my comments here:
    * Update slideMaster ids to prevent corrupted pptx.
    * - Take first slideMaster id from presentation.xml to start,
    * - then update incremental ids of each p:sldLayoutId in slideMaster[i].xml
    * (starting by slideMasterId + 1)
    * - and update next slideMaster id with previous p:sldLayoutId + 1
    *
    * p:sldMasterId-ids and p:sldLayoutId-ids need to be in a row, otherwise
    * PowerPoint will complain on any p:sldLayoutId-id lower than its
    * corresponding slideMaster-id. omg.
  • I could not figure out why deleting the theme files causes a corrupted pptx on.
  • This will eventually not remove e.g. the images placed on a removed slide master.

Please let me know if this is useful!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request refactor
Projects
None yet
Development

When branches are created from issues, their pull requests are automatically linked.

3 participants