简体   繁体   中英

R officer package: Add slide numbers that reflect current slide position

I'm an avid user of the Reporters and Officer packages and currently trying to transition to Officer for a Powerpoint workflow. I'm using a slide template that includes slide number placeholders in the master.

When using Reporters, I am able to add the slide numbers using doc <-addPageNumber( doc ) and the page numbers reflect the current position each slide has in the deck. I am looking for the same functionality in Officer, and looking for the slide numbers to update appropriately when I move the slides.

When I use ph_with_text(doc, type = "sldNum", str = "slide 1") , I am required to supply a string with a static number or text, and it does not update according to where the slide appears in the deck. For example, if I know my slide will be slide 2, I can enter str = "2" , but then the slide number will read as 2 even if I move that slide to the slide 3 position in the presentation.

I tried leaving the string empty with str = "" or with ph_empty(type= "sldNum") but these result in the string "Slide Number" appearing on the slide.

Any help or pointers in the right direction would be appreciated!

I've successfully added page numbers using officer 0.3.4 with a for loop after I'm done with the presentation.

library(officer)
library(magrittr)

my_pres <- read_pptx()  %>% 
  add_slide('Title Only', 'Office Theme') %>%
  ph_with(value = 'Slide 2 Title', location = ph_location_type(type = "title")) %>%
  add_slide('Title Only', 'Office Theme') %>%
  ph_with(value = 'Slide 3 Title', location = ph_location_type(type = 'title')) 

# add slide numbers starting on slide 2

n_slides <- length(my_pres)
for (i_slide in 2:n_slides) {
  my_pres <- my_pres %>%
    on_slide(index = i_slide) %>%
    ph_with(value = i_slide, location = ph_location_type('sldNum'))
}

Having faced a similar issue with officer and taking a look in the source code I came up with the following solution

ph_with_text_fld(doc, type = "sldNum", str = "2")

The code for that function follows:

library(htmltools)
library(xml2)

ph_with_text_fld <- function( x, str, type = "title", index = 1 ){

  stopifnot( type %in% c("ctrTitle", "subTitle", "dt", "ftr", "sldNum", "title", "body") )

  slide <- x$slide$get_slide(x$cursor)
  sh_pr_df <- slide$get_xfrm(type = type, index = index)
  sh_pr_df$str <- str
  xml_elt <- do.call(pml_shape_str_fld, sh_pr_df)
  node <- as_xml_document(xml_elt)

  xml_add_child(xml_find_first(slide$get(), "//p:spTree"), node)

  slide$fortify_id()
  x
}

pml_shape_str_fld <- function(str, ph, offx, offy, cx, cy, ...) {

  sp_pr <- sprintf("<p:spPr><a:xfrm><a:off x=\"%.0f\" y=\"%.0f\"/><a:ext cx=\"%.0f\" cy=\"%.0f\"/></a:xfrm></p:spPr>", offx, offy, cx, cy)
  # sp_pr <- "<p:spPr/>"
  nv_sp_pr <- "<p:nvSpPr><p:cNvPr id=\"\" name=\"\"/><p:cNvSpPr><a:spLocks noGrp=\"1\"/></p:cNvSpPr><p:nvPr>%s</p:nvPr></p:nvSpPr>"
  nv_sp_pr <- sprintf( nv_sp_pr, ifelse(!is.na(ph), ph, "") )
  paste0( pml_with_ns("p:sp"),
          nv_sp_pr, sp_pr,
          "<p:txBody><a:bodyPr/><a:lstStyle/><a:p><a:fld id=\"{GUID FROM THE MASTER TEMPLATE}\" type=\"slidenum\"><a:rPr/><a:t>",
          htmlEscape(str),
          "</a:t></a:fld></a:p></p:txBody></p:sp>"
  )
}

pml_with_ns <- function(x){
  base_ns <- "xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\" xmlns:p=\"http://schemas.openxmlformats.org/presentationml/2006/main\""
  sprintf("<%s %s>", x, base_ns)
}

The important part is

<a:fld id=\"{GUID FROM THE MASTER TEMPLATE}\" type=\"slidenum\">

where GUID FROM THE MASTER TEMPLATE needs to be replaced with the GUID from the Slidemaster Layout for that Field

I came here looking for a more elegant solution than I have, but I thought I would at least offer this because it does technically solve your/our problem, just inelegantly.

It makes it easier to move around the slides without worrying about the slide numbers, but it does at a little cruft as well and you have to be careful to increment it even if you don't have a slide number slot (as in the case of my "Title" slide).

An alternative is to write yourself a simple increment operator a la this question .

presentation_name_here <- officer::read_pptx("Presentations/Template.pptx")

slide_number <- 1

# Title slide -----------------------------------------------------------
presentation_name_here <- presentation_name_here %>%
  add_slide(layout = "Title Slide", master = "Office Theme") %>%
  ph_with(value = "Title", location = ph_location_label(ph_label = "Title")) %>%
slide_number <- slide_number + 1

# Executive summary -----------------------------------------------------
presentation_name_here <- presentation_name_here %>%
  add_slide(layout = "Title and Content", master = "Office Theme") %>%
  ph_with(value = "Executive summary", location = ph_location_label(ph_label = "Title")) %>%
  ph_with(value = slide_number, location = ph_location_label(ph_label = "Slide Number")) %>%
slide_number <- slide_number + 1

# Dashboard ---------------------------------------------------------------
presentation_name_here <- presentation_name_here %>%
  add_slide(layout = "Title and Content", master = "Office Theme") %>%
  ph_with(value = "Dashboard", location = ph_location_label(ph_label = "Title")) %>%
  ph_with(value = slide_number, location = ph_location_label(ph_label = "Slide Number"))

I've add my own code to look up the GUIDs based on András's answer :

get.guid = function(doc, xml.file){

  xml.file.path = doc$slideLayouts$.__enclos_env__$private$collection[[xml.file]]$file_name()

  layout.xml = read_xml(xml.file.path)

  layout.xml %>% 
    xml_find_first("//a:fld[@type=\"slidenum\"]") %>%
    xml_attr("id") 

}

ph_with_text_fld <- function( x, str, type = "title", index = 1, slide_layout_name){

  stopifnot( type %in% c("ctrTitle", "subTitle", "dt", "ftr", "sldNum", "title", "body") )

  slide <- x$slide$get_slide(x$cursor)

  xml.file = slide$get_metadata()$layout_file %>% basename 
  guid = get.guid(x, xml.file)

  sh_pr_df <- slide$get_xfrm(type = type, index = index)
  sh_pr_df$str <- str
  sh_pr_df$guid <- guid
  xml_elt <- do.call(pml_shape_str_fld, sh_pr_df)
  node <- as_xml_document(xml_elt)

  xml_add_child(xml_find_first(slide$get(), "//p:spTree"), node)

  slide$fortify_id()
  x
}

pml_shape_str_fld <- function(str, ph, offx, offy, cx, cy, guid, ...) {

  sp_pr <- sprintf("<p:spPr><a:xfrm><a:off x=\"%.0f\" y=\"%.0f\"/><a:ext cx=\"%.0f\" cy=\"%.0f\"/></a:xfrm></p:spPr>", offx, offy, cx, cy)
  # sp_pr <- "<p:spPr/>"
  nv_sp_pr <- "<p:nvSpPr><p:cNvPr id=\"\" name=\"\"/><p:cNvSpPr><a:spLocks noGrp=\"1\"/></p:cNvSpPr><p:nvPr>%s</p:nvPr></p:nvSpPr>"
  nv_sp_pr <- sprintf( nv_sp_pr, ifelse(!is.na(ph), ph, "") )
  paste0( pml_with_ns("p:sp"),
          nv_sp_pr, sp_pr,
          "<p:txBody><a:bodyPr/><a:lstStyle/><a:p><a:fld id=\"", guid, "\" type=\"slidenum\"><a:rPr/><a:t>",
          htmlEscape(str),
          "</a:t></a:fld></a:p></p:txBody></p:sp>"
  )
}

pml_with_ns <- function(x){
  base_ns <- "xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\" xmlns:p=\"http://schemas.openxmlformats.org/presentationml/2006/main\""
  sprintf("<%s %s>", x, base_ns)
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM