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

master_name appear as NA in read_pptx, resulting in 'attempt to apply non-function' error on ph_with #488

Open
csbu00 opened this issue Apr 6, 2023 · 3 comments
Labels

Comments

@csbu00
Copy link

csbu00 commented Apr 6, 2023

I created a simple slide template with a master template and several layouts, along with their placeholder (attached). Oddly, when I try to read_pptx, it seems to not be picking up the master template's name (it's reading as a null value)? This seems to result in error when using ph_with - I'm guessing it's because it couldn't process a null value. Have I perhaps done something wrong, either in the template creation or code, or is this a bug? Thank you!

Slide Template_R.pptx

library('tidyverse')
library('officer')

presentation <- officer::read_pptx('Slide Template_R.pptx')
template_object_map <- presentation %>% officer::layout_properties() %>% select(c('master_name','name', 'type', 'id', 'ph_label')) 

my_pres <- presentation %>% 
			remove_slide(index = 1) %>% 
			add_slide(master=NA, layout='Style_leftright_a') %>%
			ph_with(value= 'Corridors', location = ph_location_label(ph_label = "Big Title")) 
@davidgohel
Copy link
Owner

thanks, I can reproduce

From presentation |> layout_summary(), I can see your master is named 'Custom Design', you can use the following code

library('tidyverse')
library('officer')

presentation <- officer::read_pptx('~/Downloads/Slide.Template_R (1).pptx')
template_object_map <- presentation %>% officer::layout_properties() %>% select(c('master_name','name', 'type', 'id', 'ph_label'))

my_pres <- presentation %>%
  remove_slide(index = 1) %>%
  add_slide(master='Custom Design', layout='Style_leftright_a') %>%
  ph_with(value= 'Corridors', location = ph_location_label(ph_label = "Big Title"))

print(my_pres, target = "Custom Design.pptx")

There is an issue with template_object_map <- presentation %>% officer::layout_properties() that should not gives you NA. Thanks for reporting

@davidgohel davidgohel added the bug label Apr 6, 2023
@csbu00
Copy link
Author

csbu00 commented Apr 6, 2023

That worked! Thank you so much!

@markheckmann
Copy link
Contributor

Analysis

The reason is that the master slide is empty. It has no objects (shapes, pics, images etc. on it). (NB: Also first the slide layout has not objects on it.) Now, when loading the PPTX via read_pptx(), the master_xfrm data frame in the following code has zero rows.

obj$slideLayouts <- dir_layout$new( package_dir,
    master_metadata = obj$masterLayouts$get_metadata(),
    master_xfrm = obj$masterLayouts$xfrm() )

The reason is, that the slide_master's xfrm() method searches all shapes, pics etc. nodes in the master's shape trees.

xfrm = function(){
  nodeset <- xml_find_all( self$get(), as_xpath_content_sel("p:cSld/p:spTree/") )
  read_xfrm(nodeset, self$file_name(), self$name())
}

However, as there are no objects in the master slide, nodeset is an empty set. read_xfrm() will then return a dataframe with zero rows as the length(nodeset) < 1) condition is met. So, while the master name "Custom Design" is passed correctly via the name arg to read_xfrm(), it is lost for further processing.

xfrmize() later uses the master_xfrm data frame with zero rows. As this is the only source used for the master layout's name, it gets lost.

xfrmize <- function(slide_xfrm, master_xfrm) {
  x <- as.data.frame(slide_xfrm)

  master_ref <- unique(data.frame(
    file = master_xfrm$file,
    master_name = master_xfrm$name,
    stringsAsFactors = FALSE
  ))
  master_xfrm <- fortify_master_xfrm(master_xfrm)

Suggestion

I tried catching the edge case by adding the following to read_xfrm().

read_xfrm <- function(nodeset, file, name){
  if( length(nodeset) < 1 && !( is.na(name) || is.null(name)) ) {
    return(data.frame(stringsAsFactors = FALSE,
                      type = NA_character_,
                      id = NA_character_,
                      ph_label = NA_character_,
                      ph = NA_character_,
                      file = basename(file),
                      offx = NA_integer_,
                      offy = NA_integer_,
                      cx = NA_integer_,
                      cy = NA_integer_,
                      rotation = NA_integer_,
                      name = name,
                      fld_id = NA_character_,
                      fld_type = NA_character_
    ))
  }

However, I do not really oversee the consequences of this and one test also fails.

I think having zero objects on a master is quite a rare edge case. The simplest would be simply to warn the user in this case.

@davidgohel What do you think?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants