diff --git a/.Rbuildignore b/.Rbuildignore new file mode 100644 index 0000000..3eebc40 --- /dev/null +++ b/.Rbuildignore @@ -0,0 +1,7 @@ +^rdocx\.Rproj$ +^\.Rproj\.user$ +^README\.Rmd$ +^LICENSE\.md$ +.gitlab-ci.yml +_pkgdown.yml +^\.github$ diff --git a/.github/.gitignore b/.github/.gitignore new file mode 100644 index 0000000..2d19fc7 --- /dev/null +++ b/.github/.gitignore @@ -0,0 +1 @@ +*.html diff --git a/.github/workflows/check_package.yml b/.github/workflows/check_package.yml new file mode 100644 index 0000000..defe3cc --- /dev/null +++ b/.github/workflows/check_package.yml @@ -0,0 +1,52 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +on: + push: + branches: [main, master] +pull_request: + branches: [main, master] + +name: R-CMD-check.yaml + +permissions: read-all + +jobs: + R-CMD-check: + runs-on: ${{ matrix.config.os }} + +name: ${{ matrix.config.os }} (${{ matrix.config.r }}) + +strategy: + fail-fast: false +matrix: + config: + - {os: macos-latest, r: 'release'} +- {os: windows-latest, r: 'release'} +- {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} +- {os: ubuntu-latest, r: 'release'} +- {os: ubuntu-latest, r: 'oldrel-1'} + +env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} +R_KEEP_PKG_SOURCE: yes + +steps: + - uses: actions/checkout@v4 + +- uses: r-lib/actions/setup-pandoc@v2 + +- uses: r-lib/actions/setup-r@v2 +with: + r-version: ${{ matrix.config.r }} +http-user-agent: ${{ matrix.config.http-user-agent }} +use-public-rspm: true + +- uses: r-lib/actions/setup-r-dependencies@v2 +with: + extra-packages: any::rcmdcheck +needs: check + +- uses: r-lib/actions/check-r-package@v2 +with: + upload-snapshots: true +build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' \ No newline at end of file diff --git a/.github/workflows/coverage_and_build.yml b/.github/workflows/coverage_and_build.yml new file mode 100644 index 0000000..5f9c9b3 --- /dev/null +++ b/.github/workflows/coverage_and_build.yml @@ -0,0 +1,80 @@ +# Workflow derived from https://github.com/r-lib/actions/tree/v2/examples +# Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help +# From {cards} actions +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + workflow_dispatch: + +name: Test and build pages + +permissions: read-all + +jobs: + coverage_and_build: + runs-on: ubuntu-latest + env: + GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} + permissions: + contents: write + concurrency: + group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }} + + steps: + - uses: actions/checkout@v4 + + - uses: r-lib/actions/setup-pandoc@v2 + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + - uses: r-lib/actions/setup-r-dependencies@v2 + with: + extra-packages: any::covr, any::pkgdown, local::. + needs: coverage, check, website + + - name: Test coverage + run: | + cov <- covr::package_coverage( + quiet = FALSE, + clean = FALSE, + install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package") + ) + covr::to_cobertura(cov) + shell: Rscript {0} + + - uses: codecov/codecov-action@v4 + with: + fail_ci_if_error: ${{ github.event_name != 'pull_request' && true || false }} + file: ./cobertura.xml + plugin: noop + disable_search: true + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Show testthat output + if: always() + run: | + ## -------------------------------------------------------------------- + find '${{ runner.temp }}/package' -name 'testthat.Rout*' -exec cat '{}' \; || true + shell: bash + + - name: Upload test results + if: failure() + uses: actions/upload-artifact@v4 + with: + name: coverage-test-failures + path: ${{ runner.temp }}/package + - name: Build site + run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE) + shell: Rscript {0} + + - name: Deploy to GitHub pages 🚀 + if: github.event_name != 'pull_request' + uses: JamesIves/github-pages-deploy-action@v4.5.0 + with: + clean: false + branch: gh-pages + folder: docs \ No newline at end of file diff --git a/DESCRIPTION b/DESCRIPTION new file mode 100644 index 0000000..9ea926b --- /dev/null +++ b/DESCRIPTION @@ -0,0 +1,58 @@ +Package: rdocx +Title: rdocx +Description: The `rdocx` package streamlines clinical trial reporting + by enabling R-driven, end-to-end workflows that produce reports in + Microsoft Word format for interdisciplinary collaboration. Built with + an object-oriented design, `rdocx` provides classes for creating + structured report items (tables, figures, etc.) with a comprehensive + suite of tests for quality assurance. The package allows rendering + reports in a customizable Word template, promoting seamless + integration into GxP-compliant environments. Two main use cases are + supported, a generic RMarkdown report template for standardized + document creation, and an automated reporting tool for selective + updates to tables and figures in existing Word files. +Version: 1.0.0 +Authors@R: c( + person("Janina", "Linnik", email = "janina.linnik@novartis.com", role = c("aut", "cre")), + person("Orla", "Doyle", email = "orla.doyle@novartis.com", role = c("aut")), + person("Ines", "Gimeno Molina", email = "ines.gimeno_molina@novartis.com", role = c("aut")), + person("Jack", "Talboys", email = "jack.talboys@novartis.com", role = c("ctb"))) +Maintainer: Janina Linnik , Orla Doyle , Ines Gimeno Molina +License: MIT + file LICENSE +Encoding: UTF-8 +Roxygen: list(markdown = TRUE) +RoxygenNote: 7.3.1 +Imports: + rmarkdown, + dplyr, + flextable, + knitr, + lgr, + officer, + R6, + checkmate, + jpeg, + magick, + magrittr, + png, + stats, + stringr, + tools, + utils, + xml2, + yaml, + cowplot, + ggplot2, + kableExtra, + testthat +VignetteBuilder: knitr +Collate: + 'ActivityTable.R' + 'ChangelogTable.R' + 'ReplaceOutputs.R' + 'SignaturesTable.R' + 'TitlePage.R' + 'rdocx_global.R' + 'rmd_render.R' + 'utils.R' + 'utils_checks.R' diff --git a/LICENSE b/LICENSE index 93c6cca..caf75e1 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ -MIT License +# MIT License -Copyright (c) 2024 Novartis +Copyright (c) 2023-2024 Novartis, rdocx authors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..489eab2 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,22 @@ +# MIT License + +Copyright (c) 2023-2024 Novartis, rdocx authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/NAMESPACE b/NAMESPACE new file mode 100644 index 0000000..3e61c84 --- /dev/null +++ b/NAMESPACE @@ -0,0 +1,28 @@ +# Generated by roxygen2: do not edit by hand + +export(ActivityTable) +export(ChangelogTable) +export(ChangelogTableRow) +export(ReplaceOutputs) +export(SignaturesTable) +export(SignaturesTableRow) +export(TitlePage) +export(generic_report_template) +export(rmd_render) +import(R6) +import(checkmate) +import(dplyr) +import(flextable) +import(jpeg) +import(lgr) +import(magick) +import(officer) +import(png) +import(rmarkdown) +import(stats) +import(stringr) +import(tools) +import(utils) +import(xml2) +import(yaml) +importFrom(magrittr,`%>%`) diff --git a/R/ActivityTable.R b/R/ActivityTable.R new file mode 100644 index 0000000..c4d304b --- /dev/null +++ b/R/ActivityTable.R @@ -0,0 +1,179 @@ +# Copyright (c) 2023-2024 Novartis, rdocx authors +# +# This file is part of rdocx. +# +# Licensed under the MIT license: +# +# http://www.opensource.org/licenses/mit-license.php +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +#' @title Activity Table class definition +#' +#' @description This R6 class relates to the activity table in the generic report +#' that documents the person who perfomed the activity and the person who validated +#' and the respective dates. +#' Developer note: This class is developed using private and active fields rather +#' than public fields directly which facilitates more rigid behaviour after the +#' class has been initialised. If it is deemed that edit the fields after initialisation +#' is a rare case then we might consider simplifying these classes to use public +#' fields. +#' +#' @field activity Active binding for setting the activity +#' @field main_author_name Active binding for setting the main author name +#' @field main_activity_date Active binding for setting the main acitivty date +#' @field qc_author_name Active binding for setting the QC author name +#' @field qc_activity_date Active binding for setting the QC activity date +#' +#' @export +#' @import R6 +#' +ActivityTable <- R6::R6Class( + "ActivityTable", + private = list( + .activity = NA_character_, + .main_author_name = NA_character_, + .main_activity_date = NA_character_, + .qc_author_name = NA_character_, + .qc_activity_date = NA_character_ + ), + active = list( + activity = function(value) { + if (missing(value)) { + private$.activity + } else { + private$.activity <- checkmate::assert_string(value) + self + } + }, + main_author_name = function(value) { + if (missing(value)) { + private$.main_author_name + } else { + private$.main_author_name <- check_name(value) + self + } + }, + main_activity_date = function(value) { + if (missing(value)) { + private$.main_activity_date + } else { + private$.main_activity_date <- check_string_is_date(value) + self + } + }, + qc_author_name = function(value) { + if (missing(value)) { + private$.qc_author_name + } else { + private$.qc_author_name <- check_name(value) + self + } + }, + qc_activity_date = function(value) { + if (missing(value)) { + private$.qc_activity_date + } else { + private$.qc_activity_date <- check_string_is_date(value) + self + } + } + + ), + public = list( + + #' @param activity Name of the activity that was performed + #' @param main_author_name Name (Firstname Lastname) of the person who performed the activity + #' @param main_activity_date Date (DD-Mmm-YYYY) when activity was performed. + #' @param qc_author_name Name (Firstname Lastname) of reviewer. + #' @param qc_activity_date Date (DD-Mmm-YYYY) when QC was performed. + #' + #' @export + #' + #' @examples + #' at <- ActivityTable$new( + #' activity = 'Super cool calculation', + #' main_author_name = 'David Bowie', + #' main_activity_date = '01-Oct-2024', + #' qc_author_name = 'Freddie Mercury', + #' qc_activity_date = '20-Oct-2024') + #' + initialize = function(activity = NA_character_, + main_author_name = NA_character_, + main_activity_date = NA_character_, + qc_author_name = NA_character_, + qc_activity_date = NA_character_ + ) { + private$.activity <- checkmate::assert_string(activity) + private$.main_author_name <- check_name(main_author_name) + private$.main_activity_date <- check_string_is_date(main_activity_date) + private$.qc_author_name <- check_name(qc_author_name) + private$.qc_activity_date <- check_string_is_date(qc_activity_date) + }, + + + #' @description Show activity table. Takes an object of class \code{\link[rdocx]{ActivityTable}}. + #' as input and generates Table 1 of the Generic Report Document. + #' + #' @export + #' @import flextable + #' @importFrom magrittr `%>%` + #' + #' @param act An object of class \code{\link[rdocx]{ActivityTable}}. + #' @return Table 1 of the Sample Size Document. + #' + #' @examples + #' at <- ActivityTable$new( + #' activity = 'Super cool calculation', + #' main_author_name = 'David Bowie', + #' main_activity_date = '01-Oct-2024', + #' qc_author_name = 'Freddie Mercury', + #' qc_activity_date = '20-Oct-2024') + #' + #' at$get_table() + #' + get_table = function(){ + + activity_row_1 <- paste0(self$activity, " performed by") + activity_row_2 <- paste0(self$activity, " reviewed by") + + activity_table <- data.frame( + Activity = c(activity_row_1, + activity_row_2), + Name = c(self$main_author_name, self$qc_author_name), + Date = c(self$main_activity_date,self$qc_activity_date) + ) %>% + flextable::flextable() %>% + table_styling() %>% + flextable::set_caption( + flextable::as_paragraph( + flextable::as_chunk("Table 1-1. People involved in the activity performed", + props = flextable::fp_text_default(font.family = "Helvetica", + italic = TRUE, + font.size = 9) + ) + ), + style = "Table Caption", + autonum = "autonum" + ) + activity_table + } + + ) +) diff --git a/R/ChangelogTable.R b/R/ChangelogTable.R new file mode 100644 index 0000000..d99946c --- /dev/null +++ b/R/ChangelogTable.R @@ -0,0 +1,248 @@ +# Copyright (c) 2023-2024 Novartis, rdocx authors +# +# This file is part of rdocx. +# +# Licensed under the MIT license: +# +# http://www.opensource.org/licenses/mit-license.php +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +#' @title Changelog Table Row class definition +#' +#' @description This R6 class controls one row of the Changelog table that documents +#' the date, version, reason and what changed in the update on the document. +#' +#' @field date Active binding for setting the date of changes +#' @field version Active binding for setting the version of the document +#' @field why_update Active binding for setting the reason for the update +#' @field what_changed Active binding for setting the section and title impacted +#' +#' @export +#' @import R6 +#' +ChangelogTableRow <- R6::R6Class( + "ChangelogTableRow", + private = list( + .date = NA_character_, + .version = NA_character_, + .why_update = NA_character_, + .what_changed = NA_character_ + ), + active = list( + date = function(value) { + if (missing(value)) { + private$.date + } else { + private$.date <- check_string_is_date(value) + self + } + }, + version = function(value) { + if (missing(value)) { + private$.version + } else { + private$.version <- checkmate::assert_string(value) + self + } + }, + why_update = function(value) { + if (missing(value)) { + private$.why_update + } else { + private$.why_update <- checkmate::expect_string(value, + pattern = ' ', + info = "Expected a sentence with spaces but + entry was a string with no spaces.") + self + } + }, + what_changed = function(value) { + if (missing(value)) { + private$.what_changed + } else { + private$.what_changed <- checkmate::expect_string(value, + pattern = ' ', + info = "Expected a sentence with spaces but + entry was a string with no spaces.") + self + } + } + + ), + + public = list( + + #' @param date String. Date (DD-Mmm-YYYY) when modifications were performed + #' @param version String. Document version + #' @param why_update String. Reason for updating the document + #' @param what_changed String. Sections and titles that where impacted with the modifications + #' + #' @examples + #' ctr <- ChangelogTableRow$new( + #' date = '01-Oct-2024', + #' version = 'Initial version', + #' why_update = 'Create first version of the document', + #' what_changed = 'Document creation') + #' + initialize = function(date = NA_character_, + version = NA_character_, + why_update = NA_character_, + what_changed = NA_character_ + ) { + private$.date <- check_string_is_date(date) + private$.version <- checkmate::assert_string(version) + private$.why_update <- checkmate::expect_string(why_update, + pattern = ' ', + info = "Expected a sentence with spaces but + entry was a string with no spaces.") + private$.what_changed <- checkmate::expect_string(what_changed, + pattern = ' ', + info = "Expected a sentence with spaces but + entry was a string with no spaces.") + + } + ) +) + +#' @title Changelog Table class definition +#' +#' @description This R6 class controls one row of the Changelog table that documents +#' the date, version, reason and what changed in the update on the document. +#' It uses the ChangelogTableRow class to manage the different rows in the table, +#' and control the checks +#' +#' @field rows List where new rows are added +#' @export +#' @import R6 +#' + +ChangelogTable <- R6::R6Class( + "ChangelogTable", + public = list( + rows = list(), + + + #' @description Takes an object of class ChangelogTableRow. + #' as input and adds it to the list of rows. + #' + #' @export + #' @importFrom magrittr `%>%` + #' + #' @param changelog_table_row_object object of class ChangelogTableRow + #' + #' @examples + #' ctr <- ChangelogTableRow$new( + #' date = '01-Oct-2024', + #' version = 'Initial version', + #' why_update = 'Create first version of the document', + #' what_changed = 'Document creation') + #' + #' changelog_table <- ChangelogTable$new() + #' changelog_table$add_row(ctr) + #' + add_row = function(changelog_table_row_object) { + checkmate::assert_class(changelog_table_row_object, "ChangelogTableRow") + self$rows <- c(self$rows, list(changelog_table_row_object)) + }, + + #' @description Create a dataframe from all the rows in the row + #' + #' @importFrom magrittr `%>%` + #' + #' + #' @examples + #' ctr <- ChangelogTableRow$new( + #' date = '01-Oct-2024', + #' version = 'Initial version', + #' why_update = 'Create first versoin of the document', + #' what_changed = 'Document creation') + #' + #' changelog_table <- ChangelogTable$new() + #' changelog_table$add_row(ctr) + #' changelog_table$to_dataframe() + #' + to_dataframe = function() { + data_frame <- do.call(rbind, lapply(self$rows, function(row) { + data.frame( + date = row$date, + version = row$version, + why_update = row$why_update, + what_changed = row$what_changed + ) + })) + colnames(data_frame) <- c("Date", + "Version", + "Why we changed it", + "What changed") + return(data_frame) + }, + + #' @description Generates the changelog table usin to_dataframe() + #' + #' @export + #' @docType methods + #' @importFrom magrittr `%>%` + #' + #' + #' @examples + #' ctr_1 <- ChangelogTableRow$new( + #' date = '01-Oct-2024', + #' version = 'Initial version', + #' why_update = 'Create first versoin of the document', + #' what_changed = 'Document creation') + #' + #' ctr_2 <- ChangelogTableRow$new( + #' date = '15-Oct-2024', + #' version = 'Final version', + #' why_update = 'Change calculation of param 1', + #' what_changed = 'Section 2') + #' + #' changelog_table <- ChangelogTable$new() + #' changelog_table$add_row(ctr_1) + #' changelog_table$add_row(ctr_2) + #' changelog_table$get_table() + #' + + get_table = function() { + changelog_table <- self$to_dataframe() %>% + flextable::flextable() %>% + table_styling() + changelog_table + } + ) +) + + + + + + + + + + + + + + + + + + diff --git a/R/ReplaceOutputs.R b/R/ReplaceOutputs.R new file mode 100644 index 0000000..e2cf2fd --- /dev/null +++ b/R/ReplaceOutputs.R @@ -0,0 +1,313 @@ +# Copyright (c) 2023-2024 Novartis, rdocx authors +# +# This file is part of rdocx. +# +# Licensed under the MIT license: +# +# http://www.opensource.org/licenses/mit-license.php +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +#' @title Replace Outputs in a word document +#' +#' @description This R6 class allows to, given a template word document and a list +#' of outputs, to add them at the given location in the text. It automates the +#' time-consuming task of adding and formatting tables and figures to a word document. +#' The two main inputs of the class are a template docx file, in this case a clinical +#' trial report and a yaml file that will contain the names of the table/figures to be replaced +#' and pointing to the new ones. +#' The yml file, should have predefined structure and parameters to be set: +#' +#' - type. Indicates if it is a table (TBL) or a figure (FIG) +#' +#' - title. The title or caption of the table/figure in the template docx file +#' +#' - file. The name of the file (csv or image) to be used in the new version of the docx +#' +#' - widths. For tables only, a pre-defined column widths (useful for model parameter table, for example) +#' +#' - occurrence. Int. To be used when captions are duplicated for more than one +#' figure/table. To set the order they appear in the word document. +#' +#' An example of the yaml structure: +#' +#' \preformatted{ +#' output_1: +#' type: TBL +#' title: "Caption of the output 1" +#' file: output_1.csv +#' widths: ~ +#' occurrence: ~ + +#' output_2: +#' type: FIG +#' title: "Caption of the output 2" +#' file: output_2.png +#' } +#' +#' +#' @field template_docx_filename Active binding for setting the path to the template docx +#' @field outputs_path Active binding for setting the path to the folder with the outputs +#' @field doc_final_filename Active binding for setting the path for the updated docx file +#' @field yml_filename Active binding for setting the path to the yaml file +#' +#' @export +#' @import R6 +#' @import checkmate +#' +ReplaceOutputs <- R6::R6Class( + "ReplaceOutputs", + private = list( + .template_docx_filename = NA_character_, + .outputs_path = NA_character_, + .doc_final_filename = NA_character_, + .yml_filename = NA_character_ + ), + active = list( + template_docx_filename = function(value){ + if (missing(value)){ + private$.template_docx_filename + } else { + checkmate::expect_file_exists(value, extension = "docx") + private$.template_docx_filename <- value + self + } + }, + outputs_path = function(value){ + if (missing(value)){ + private$.outputs_path + } else { + checkmate::assert_directory_exists(value) + private$.outputs_path <- value + self + } + }, + doc_final_filename = function(value){ + if (missing(value)){ + private$.doc_final_filename + } else { + checkmate::expect_path_for_output(value, overwrite = TRUE) + private$.doc_final_filename <- value + self + } + }, + yml_filename = function(value){ + if (missing(value)){ + private$.yml_filename + } else { + if (!is.na(value)){ + checkmate::expect_file_exists(value, extension = c("yml", "yaml")) + private$.yml_filename <- value + } else { + private$.yml_filename <- value + } + self + } + } + ), + public = list( + + #' @param template_docx_filename String. File name of the template docx to be + #' modified. For example, "/path/to/file/filename.docx" + #' @param outputs_path String. Path to the folder with the outputs (figures + #' and tables-csvs) that will be added to the new version of the docx + #' @param doc_final_filename String. File name of the updated version of the + #' docx file. For example, "/path/to/file/filename_updated.docx" + #' @param yml_filename String. File name of the yaml file that will guide the + #' replacement of the outputs (tables and figures) in the updated docx. For + #' example, "/path/to/file/filename.yml" + #' + #' @import checkmate + #' @export + #' + #' @examples + #' uo <- ReplaceOutputs$new( + #' template_docx_filename = system.file("use_cases/02_automated_reporting", + #' "Automated_Reporting_Example.docx", + #' package="rdocx"), + #' outputs_path = system.file("use_cases/02_automated_reporting/example_outputs", + #' package="rdocx"), + #' doc_final_filename = "./test.docx", + #' yml_filename = system.file("use_cases/02_automated_reporting", + #' "example_yml_1.yml", + #' package="rdocx") + #' ) + initialize = function(template_docx_filename = NA_character_, + outputs_path = NA_character_, + doc_final_filename = NA_character_, + yml_filename = NA_character_) { + + # Checks + checkmate::expect_file_exists(template_docx_filename, extension = "docx") + private$.template_docx_filename <- template_docx_filename + private$.outputs_path <- checkmate::assert_directory_exists(outputs_path) + + + private$.doc_final_filename <- checkmate::assert_path_for_output(doc_final_filename, overwrite = TRUE) + + if (!is.na(yml_filename)){ + checkmate::expect_file_exists(yml_filename, extension = c("yml", "yaml")) + private$.yml_filename <- yml_filename + } else { + private$.yml_filename <- yml_filename + } + }, + #' + #' @description Get captions to yaml + #' Given a docx file, it scans the document to find the Figures and Tables captions + #' and provides a yaml file with all the figures and tables found. It also scans + #' the document for possible "Source:" which has to be after a placeholder figure/table. + #' If source was found, it will be added to "file" param in the yaml file. + #' + #' @param yml_caption_filename String. File name of the newly created yaml + #' file that will contain the Figures and Tables of in the provided docx document. + #' For example, "/path/to/file/filename.yml" + #' It will follow the required format for the replacement of the outputs (tables + #' and figures) to update docx. + #' \preformatted{ + #' output_1: + #' type: TBL + #' title: "Caption of the output 1" + #' file: ~ + #' widths: ~ + #' occurrence: ~ + + #' output_2: + #' type: FIG + #' title: "Caption of the output 2" + #' file: ~ + #' widths: ~ + #' occurrence: ~ + #' } + #' + #' @export + #' + #' @examples + #' uo <- ReplaceOutputs$new( + #' template_docx_filename = system.file("use_cases/02_automated_reporting", + #' "Automated_Reporting_Example.docx", + #' package="rdocx"), + #' outputs_path = system.file("use_cases/02_automated_reporting/example_outputs", + #' package="rdocx"), + #' doc_final_filename = "./test.docx" + #' ) + #' uo$get_captions_to_yml(yml_caption_filename="./test_yml.yml") + get_captions_to_yml = function(yml_caption_filename) { + list_of_captions <- extract_figure_table_captions(private$.template_docx_filename) + create_yml(list_of_captions, yml_caption_filename) + }, + + #' + #' @description Update all outputs. + #' It iterates through all the outputs in the yml file, and assigns them in the + #' correct location in the updated docx. + #' If no "file" param given (NA or NULL), it will skip the output. + #' + #' @import yaml + #' @import officer + #' @import tools + #' @import lgr + #' @export + #' + #' @examples + + #' uo <- ReplaceOutputs$new( + #' template_docx_filename = system.file("use_cases/02_automated_reporting", + #' "Automated_Reporting_Example.docx", + #' package="rdocx"), + #' outputs_path = system.file("use_cases/02_automated_reporting/example_outputs", + #' package="rdocx"), + #' doc_final_filename = "./test.docx", + #' yml_filename = system.file("use_cases/02_automated_reporting", + #' "example_yml_1.yml", + #' package="rdocx") + #' ) + #' uo$update_all_outputs() + update_all_outputs = function(){ + + # Create path for the log file + log_filename <- paste0(tools::file_path_sans_ext(private$.doc_final_filename), ".log") + + # Remove old logger file if necessary + if (file.exists(log_filename)) { file.remove(log_filename) } + + # Set AppenderFile for logger + lg_ar <- get_logger("file_ar")$set_appenders(AppenderFile$new(log_filename)) + + # Set format of log file messages + lg_ar$appenders[[1]]$layout$set_fmt("[%t] %m") + + log_break <- "****************************************************************" + + lg_ar$info(paste("Word template filename: ", private$.template_docx_filename)) + lg_ar$info(paste("Outputs path: ", private$.outputs_path)) + lg_ar$info(paste("Yml filename: ", private$.yml_filename)) + lg_ar$info(paste("Final doc filename: ", private$.doc_final_filename)) + lg_ar$info(paste("Date and time:", Sys.time(), Sys.timezone())) + lg_ar$info(paste("DaVinci user: ", Sys.getenv("USER"))) + lg_ar$info(log_break) + + doc <- officer::read_docx(private$.template_docx_filename) + outputs_to_change <- yaml::read_yaml(private$.yml_filename) + + # Check if duplicates + search_for_duplicates(outputs_to_change) + + for(output in outputs_to_change){ + if(!is.null(output$file)){ + lg_ar$info(paste("Output:", output$type, output$title)) + + doc_updated <- find_and_delete_output(template_docx=doc, + output_title=output$title, + output_type=output$type, + occurrence=output$occurrence) + if(toupper(output$type)=="TBL"){ + doc_updated <- change_table(template_doc = doc_updated, + full_path = private$.outputs_path, + table=output$file, + widths=output$widths) + lg_ar$info(paste("Table updated with:",file.path(private$.outputs_path, output$file))) + + } else if (toupper(output$type)=="FIG") { + doc_updated <- change_figure(template_doc = doc_updated, + full_path = private$.outputs_path, + figure = output$file) + lg_ar$info(paste("Figure updated with:", file.path(private$.outputs_path, output$file))) + } + } + } + + if (!exists('doc_updated')) { + doc_updated <- doc + lg_ar$info("Warning: The docx was not modified!") + } + + save_updated_document(doc_updated, private$.doc_final_filename) + lg_ar$info(paste0("Docx saved!! File located at: ", private$.doc_final_filename)) + + lg_ar$info(log_break) + + lg_ar$info("Information about this R session") + lg_ar$info(capture.output(.libPaths())) + lg_ar$info(log_break) + lg_ar$info(capture.output(sessionInfo())) + + } + ) +) diff --git a/R/SignaturesTable.R b/R/SignaturesTable.R new file mode 100644 index 0000000..66d2033 --- /dev/null +++ b/R/SignaturesTable.R @@ -0,0 +1,202 @@ +# Copyright (c) 2023-2024 Novartis, rdocx authors +# +# This file is part of rdocx. +# +# Licensed under the MIT license: +# +# http://www.opensource.org/licenses/mit-license.php +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +#' @title Signatures Table Row class definition +#' +#' @description This R6 class controls one row of the Signatures table that documents +#' the date, version, reason and what changed in the update on the document. +#' +#' @field name Active binding for setting the name of the person who will sign +#' @field department Active binding for setting the department of the person who signs +#' @field date Active binding for setting the date of sign +#' +#' @export +#' @import R6 +#' +SignaturesTableRow <- R6::R6Class( + "SignaturesTableRow", + private = list( + .name = NA_character_, + .department = NA_character_, + .date = NA_character_ + ), + active = list( + name = function(value) { + if (missing(value)) { + private$.name + } else { + private$.name <- check_name(value) + self + } + }, + department = function(value) { + if (missing(value)) { + private$.department + } else { + private$.department <- checkmate::assert_string(value) + self + } + }, + date = function(value) { + if (missing(value)) { + private$.date + } else { + private$.date <- check_string_is_date(value) + self + } + } + ), + + public = list( + + #' @param name String. Name of the person who will sign the document + #' @param department String. Department of the person who will sign the document + #' @param date String. Date (DD-Mmm-YYYY) when the document will be signed + #' + #' @examples + #' str <- SignaturesTableRow$new( + #' name = 'Harry Styles', + #' department = 'eCompliance', + #' date = '15-Feb-2024') + #' + initialize = function(name = NA_character_, + department = NA_character_, + date = NA_character_ + ) { + + private$.name <- check_name(name) + private$.department <- checkmate::expect_string(department) + private$.date <- check_string_is_date(date) + + } + ) +) + +#' @title Signatures Table class definition +#' +#' @description This R6 class controls one row of the Signatures table that documents +#' the date, version, reason and what changed in the update on the document. +#' It uses the ChangelogTableRow class to manage the different rows in the table, +#' and control the checks +#' +#' @field rows List where new rows are added +#' @export +#' @import R6 +#' + +SignaturesTable <- R6::R6Class( + "SignaturesTable", + public = list( + rows = list(), + + + #' @description Takes an object of class SignaturesTableRow. + #' as input and adds it to the list of rows. + #' + #' @export + #' @importFrom magrittr `%>%` + #' + #' @param signatures_table_row_object object of class SignaturesTableRow + #' + #' @examples + #' str <- SignaturesTableRow$new( + #' name = 'Harry Styles', + #' department = 'eCompliance', + #' date = '15-Feb-2024') + #' + #' + #' signatures_table <- SignaturesTable$new() + #' signatures_table$add_row(str) + #' + add_row = function(signatures_table_row_object) { + checkmate::assert_class(signatures_table_row_object, "SignaturesTableRow") + self$rows <- c(self$rows, list(signatures_table_row_object)) + }, + + #' @description Create a dataframe from all the rows in the row + #' + #' @importFrom magrittr `%>%` + #' + #' + #' @examples + #' str <- SignaturesTableRow$new( + #' name = 'Harry Styles', + #' department = 'eCompliance', + #' date = '15-Feb-2024') + #' + #' + #' signatures_table <- SignaturesTable$new() + #' signatures_table$add_row(str) + #' signatures_table$to_dataframe() + #' + to_dataframe = function() { + data_frame <- do.call(rbind, lapply(self$rows, function(row) { + data.frame( + name = row$name, + department = row$department, + date = row$date + ) + })) + colnames(data_frame) <- c("Name", + "Department", + "Date") + # Add the signature column + data_frame['Signatures'] <- rep.int(" ", nrow(data_frame)) + return(data_frame) + }, + + #' @description Generates the signature table usin to_dataframe() + #' + #' @export + #' @docType methods + #' @importFrom magrittr `%>%` + #' + #' + #' @examples + #' str_1 <- SignaturesTableRow$new( + #' name = 'Harry Styles', + #' department = 'eCompliance', + #' date = '15-Feb-2024') + #' + #' str_2 <- SignaturesTableRow$new( + #' name = 'Alex Turner', + #' department = 'Product Development', + #' date = '20-Feb-2024') + #' + #' signatures_table <- SignaturesTable$new() + #' signatures_table$add_row(str_1) + #' signatures_table$add_row(str_2) + #' signatures_table$get_table() + #' + + get_table = function() { + signature_table <- self$to_dataframe() %>% + flextable::flextable() %>% + table_styling() + signature_table + } + ) +) \ No newline at end of file diff --git a/R/TitlePage.R b/R/TitlePage.R new file mode 100644 index 0000000..dfc4155 --- /dev/null +++ b/R/TitlePage.R @@ -0,0 +1,320 @@ +# Copyright (c) 2023-2024 Novartis, rdocx authors +# +# This file is part of rdocx. +# +# Licensed under the MIT license: +# +# http://www.opensource.org/licenses/mit-license.php +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +#' @title Title page class definition +#' +#' @description This R6 class relates to the Title Page in a report that adds all +#' the required fields of the Generic Report Title Page +#' Developer note: This class is developed using private and active fields rather +#' than public fields directly which facilitates more rigid behaviour after the +#' class has been initialised. If it is deemed that edit the fields after initialisation +#' is a rare case then we might consider simplifying these classes to use public +#' fields. +#' +#' @field report_title Active binding for setting the report title +#' @field department Active binding for setting the department +#' @field study_title Active binding for setting the study title +#' @field study_number Active binding for setting the study number +#' @field status Active binding for setting the document status +#' @field version Active binding for setting the document version +#' @field date Active binding for setting the date +#' @field bus_class Active binding for setting the business classification +#' @field changelog_table Active binding for setting the changelog table +#' @field signatures_table Active binding for setting the signatures table +#' +#' @export +#' @import R6 +#' @import checkmate +#' +TitlePage <- R6::R6Class( + "TitlePage", + private = list( + .report_title = NA_character_, + .department = NA_character_, + .study_title = NA_character_, + .study_number = NA_character_, + .status = NA_character_, + .version = NA_character_, + .date = NA_character_, + .bus_class = NA_character_, + .changelog_table = NULL, + .signatures_table = NULL + ), + active = list( + report_title = function(value) { + if (missing(value)) { + private$.report_title + } else { + private$.report_title <- checkmate::expect_string(value, + pattern = " ", + info = "Expected a sentence with spaces but entry was a string with no spaces." + ) + self + } + }, + department = function(value) { + if (missing(value)) { + private$.department + } else { + private$.department <- checkmate::expect_string(value) + self + } + }, + study_title = function(value) { + if (missing(value)) { + private$.study_title + } else { + private$.study_title <- checkmate::expect_string(value, + pattern = " ", + info = "Expected a sentence with spaces but entry was a string with no spaces." + ) + self + } + }, + study_number = function(value) { + if (missing(value)) { + private$.study_number + } else { + private$.study_number <- checkmate::assert_string(value) + self + } + }, + status = function(value) { + if (missing(value)) { + private$.status + } else { + private$.status <- checkmate::assert_choice(value, c("Draft", "Final")) + self + } + }, + version = function(value) { + if (missing(value)) { + private$.version + } else { + checkmate::assert_double(as.double(value), max.len = 1, any.missing = FALSE) + private$.version <- checkmate::assert_string(value) + self + } + }, + date = function(value) { + if (missing(value)) { + private$.date + } else { + private$.date <- check_string_is_date(value) + self + } + }, + bus_class = function(value) { + if (missing(value)) { + private$.bus_class + } else { + private$.bus_class <- checkmate::assert_string(value) + self + } + }, + changelog_table = function(value) { + if (missing(value)) { + private$.changelog_table + } else { + private$.changelog_table <- check_null_or_flextable(value) + self + } + }, + signatures_table = function(value) { + if (missing(value)) { + private$.signatures_table + } else { + private$.signatures_table <- check_null_or_flextable(value) + self + } + } + ), + public = list( + + #' @param report_title String. Report title. + #' @param department String. Department. + #' @param study_title String. Study title of the document + #' @param study_number String. Study number of the document + #' @param status String. Status of the document, with possible values `Draft` or `Final` + #' @param version String. Version of the document. + #' @param date String. Date (DD-Mmm-YYYY) of the final document release. + #' @param bus_class String. Business Classification. + #' @param changelog_table Flextable. Table to be added after the title page (Optional) + #' @param signatures_table Flextable. Table to be added after the title page (Optional) + #' + #' @export + #' @import checkmate + #' @examples + #' \dontrun{ + #' tp = TitlePage$new( + #' report_title = "Super cool document", + #' department = "Cool department", + #' study_title = "Compute super cool things", + #' study_number = "OWNDJQW9923", + #' status = "Draft", + #' version = "1", + #' date = "01-Feb-2024", + #' bus_class = "Very confidential") + #' } + initialize = function(report_title = NA_character_, + department = NA_character_, + study_title = NA_character_, + study_number = NA_character_, + status = c("Draft", "Final"), + version = NA_character_, + date = NA_character_, + bus_class = NA_character_, + changelog_table = NULL, + signatures_table = NULL) { + private$.report_title <- checkmate::expect_string(report_title, + pattern = " ", + info = "Expected a sentence with spaces but entry was a string with no spaces." + ) + private$.department <- checkmate::expect_string(department) + private$.study_title <- checkmate::expect_string(study_title, + pattern = " ", + info = "Expected a sentence with spaces but entry was a string with no spaces." + ) + private$.study_number <- checkmate::assert_string(study_number) + private$.status <- checkmate::assert_choice(status, c("Draft", "Final")) + checkmate::assert_double(as.double(version), max.len = 1, any.missing = FALSE) + private$.version <- checkmate::assert_string(version) + private$.date <- check_string_is_date(date) + private$.bus_class <- checkmate::assert_string(bus_class) + private$.changelog_table <- check_null_or_flextable(changelog_table) + private$.signatures_table <- check_null_or_flextable(signatures_table) + }, + + + #' @description Generate title page. Takes an object of class \code{\link[rdocx]{TitlePage}}. + #' as input and generates the title page. + #' @aliases get_title_page + #' + #' @param reference_docx String. Template document in .docx format providing + #' styling and structure information for rendering your docx report. + #' This is configured to be Generic Report Template using the function + #' \code{generic_report_template()}. + #' @param output_path String. Path where the title page in docx format should + #' be written. Default is the current working directory. + #' + #' @export + #' @import officer + #' @importFrom magrittr `%>%` + #' + #' @return A title page in Word format ('_generic_report_title.docx'). + #' + #' @examples + #' \dontrun{ + #' tp = TitlePage$new( + #' report_title= "Super cool document", + #' department = "Cool department", + #' study_title = "Compute super cool things", + #' study_number = "OWNDJQW9923", + #' status = "Draft", + #' version = "1", + #' date = "01-Feb-2024", + #' bus_class = "Very confidential") + #' tp$get_title_page() + #' } + get_title_page = function(reference_docx = generic_report_template(), + output_path = "."){ + #check that the `reference_docx` file exists + checkmate::expect_file_exists(reference_docx, extension = "docx") + #check that output path exists and is writable and allows overwriting + checkmate::expect_path_for_output(output_path, overwrite = TRUE) + + # Read the template document + doc_template <- officer::read_docx(reference_docx) + doc_summary <- doc_template %>% + officer::docx_summary() + + # We only want the title page, so we need the index for the last line in the title + # page and the end of the doc + index_end_page <- doc_summary[doc_summary$text=="Business Classification",]$doc_index + 1 + index_end_doc <- max(doc_summary$doc_index) + + doc_template <- doc_template %>% + officer::cursor_begin() %>% + officer::cursor_reach("Business Classification") + + # Remove all pages after the title page + for (i in index_end_page:index_end_doc) { + doc_template <- doc_template %>% + officer::cursor_forward() %>% + officer::body_remove() + } + + title_page <- doc_template %>% + replace_text("Generic Report Title", self$report_title) %>% + replace_text("Department", self$department) %>% + replace_text("Study title", self$study_title) %>% + replace_text("Study number", self$study_number) %>% + replace_text("Draft/Final", self$status) %>% + replace_text("v0", self$version) %>% + officer::body_replace_all_text( + old_value = "dd-Mmm-yyyy", + new_value = self$date, + only_at_cursor = FALSE, fixed = TRUE + ) %>% + replace_text("Business Classification", self$bus_class) + + + if (!is.null(private$.changelog_table)) { + formatting <- officer::fp_text(font.size = 16, + bold = TRUE, + font.family = 'Helvetica') + section_title <- officer::ftext( + "Changelog Table - What has changed in every document version", + formatting + ) + + title_page <- title_page %>% + officer::body_add(officer::fpar(section_title)) %>% + officer::body_add_par("") %>% + flextable::body_add_flextable(private$.changelog_table) %>% + body_add_break(pos = "after") + } + + if (!is.null(private$.signatures_table)) { + formatting <- officer::fp_text(font.size = 16, + bold = TRUE, + font.family = 'Helvetica') + section_title <- officer::ftext( + "Signature pages for Generic Document", + formatting + ) + + title_page <- title_page %>% + officer::body_add(officer::fpar(section_title)) %>% + officer::body_add_par("") %>% + flextable::body_add_flextable(private$.signatures_table) %>% + body_add_break(pos = "after") + } + # Save title page separately + print(title_page, target = "_title.docx") + } + ) +) diff --git a/R/rdocx_global.R b/R/rdocx_global.R new file mode 100644 index 0000000..879bf76 --- /dev/null +++ b/R/rdocx_global.R @@ -0,0 +1,37 @@ +# Copyright (c) 2023-2024 Novartis, rdocx authors +# +# This file is part of rdocx. +# +# Licensed under the MIT license: +# +# http://www.opensource.org/licenses/mit-license.php +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +#' Generic Report Template location +#' +#' Function which returns the location of the generic report template +#' +#' @export +generic_report_template <- function(){ + system.file("use_cases/00_word_templates/generic_report", + "Generic_Report_Template.docx", + package = "rdocx") +} + diff --git a/R/rmd_render.R b/R/rmd_render.R new file mode 100644 index 0000000..44d9b3d --- /dev/null +++ b/R/rmd_render.R @@ -0,0 +1,139 @@ +# Copyright (c) 2023-2024 Novartis, rdocx authors +# +# This file is part of rdocx. +# +# Licensed under the MIT license: +# +# http://www.opensource.org/licenses/mit-license.php +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +#' Rmd Sample Size renderer +#' @description Given the filename of a generic report Rmd template, it renders it. +#' It is a wrapper that hides the process of rendering, starting by getting the +#' title and body separately, getting the study code and compound name, and merging +#' both files with the correct output name. +#' +#' @param rmd_filename String. Name of the Rmd file to be rendered. +#' @param output_path String. Set the path where your generated Word file will be written to. +#' The default path will the path of the Rmd file. +#' @param version Integer. Version number of the created Word file, e.g., 0, 1, 12. +#' Will be modified to follow version numbering guidelines, e.g., _v00, _v01, v_12. +#' @param reference_docx String. Template document in .docx format providing +#' styling and structure information for rendering your docx report. +#' This is configured to be the generic report template using the function +#' \code{generic_report_template()}. +#' +#' @return Word docx file rendered from the Rmd located in `output_path` +#' @export +#' @import rmarkdown +#' @import officer +#' @import lgr +#' +#' @examples +#' \dontrun{ +#' rmd_render(system.file("use_cases/01_generic_report", +#' "generic_report_template.Rmd", +#' package="rdocx")) +#' } +rmd_render <- function(rmd_filename, + output_path = dirname(rmd_filename), + version = 0, + reference_docx = generic_report_template()) { + # Checks ------------------------------------------------------------------ + # Check the rmd_filename arg points to a file that exists + checkmate::assert_file_exists(rmd_filename) + # check that the output_path arg is a valid directory + checkmate::assert_directory_exists(output_path) + + # Get the path of the Rmarkdown + rmd_path <- dirname(rmd_filename) + + # Get formatted version + doc_version <- version_number(version) + + # Render the document: produce _title.docx and _body.docx using the style in + # reference_docx + rmarkdown::render(rmd_filename, + output_file = "_body.docx", + output_format = word_document(toc = TRUE, reference_docx = reference_docx), + quiet = TRUE + ) + + # Open the title page to get the Report title, Study title and study number + title_summary <- officer::read_docx(file.path(rmd_path, "_title.docx")) %>% + officer::docx_summary() + generic_report_title <- title_summary$text[which(title_summary$style_name %in% "GenericReportTitle")] + study_title <- title_summary$text[which(title_summary$style_name %in% "StudyTitle")] + + # Note that `Dedicatednumber` corresponds to two fields: the study number and + # study title -- the assumption here is that study number is always first + study_number <- title_summary$text[which(title_summary$style_name %in% "StudyNumber")] + + # Create the name of the final file + final_document_name <- paste0( + study_number, + "_", + gsub(" ", "_", study_title), + "_", + gsub(" ", "_", generic_report_title), + doc_version + ) + + # Merge the title and body and write the doc to `output_path` + output <- officer::read_docx(file.path(rmd_path, "_title.docx")) %>% + officer::body_add_docx(file.path(rmd_path, "_body.docx")) %>% + officer::headers_replace_all_text("Generic Report Title", generic_report_title, warn = F) %>% + officer::headers_replace_all_text("Study title", study_title, warn = F) %>% + officer::headers_replace_all_text("Study number", study_number, warn = F) %>% + print(target = file.path(output_path, paste0(final_document_name, ".docx"))) + + # Delete the artifacts for title and body + invisible(file.remove(file.path(rmd_path, "_title.docx"))) + invisible(file.remove(file.path(rmd_path, "_body.docx"))) + + + # Set up logging ---------------------------------------------------------- + log_filename <- paste0(output_path, "/", final_document_name, ".log") + log_break <- "====================================================================================================================================" + + # Remove old logger file if necessary + if (file.exists(log_filename)) { file.remove(log_filename) } + # Set AppenderFile for logger + lg_ss <- get_logger("file_ss")$set_appenders(AppenderFile$new(log_filename)) + + # Set format of log file messages + lg_ss$appenders[[1]]$layout$set_fmt("[%t] %m") + + + # Log document details and session info --------------------------------------- + lg_ss$info("======================== Generic Report Details ============================================================================") + lg_ss$info(paste("Document name:", paste0(final_document_name, ".docx"))) + lg_ss$info(paste("Document location after rendering:", output_path)) + lg_ss$info(paste("Date and time:", Sys.time(), Sys.timezone())) + lg_ss$info("") + lg_ss$info("======================== R session info ==========================================================================================") + for ( lib in .libPaths() ) { lg_ss$info(cat(lib)) } + lg_ss$info("") + lg_ss$info(capture.output(sessionInfo())) + lg_ss$info("") + lg_ss$info(log_break) + lg_ss$info("Rendering completed.") + +} diff --git a/R/utils.R b/R/utils.R new file mode 100644 index 0000000..9499459 --- /dev/null +++ b/R/utils.R @@ -0,0 +1,611 @@ + + + +# Copyright (c) 2023-2024 Novartis, rdocx authors +# +# This file is part of rdocx. +# +# Licensed under the MIT license: +# +# http://www.opensource.org/licenses/mit-license.php +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +#' @title Replace text with officer +#' @description Search for a value in a document and replicate it with the new one +#' +#' @param doc Docx document to be modified +#' @param old_value Word to search in the doc +#' @param new_value Replacement for the old_value +#' +#' @import officer +#' @importFrom magrittr `%>%` +#' +#' @examples +#' \dontrun{ +#' replace_text("dummy_doc.docx", "title", "new title") +#' } +replace_text <- function(doc, old_value, new_value) { + doc_updated <- doc %>% + officer::cursor_reach(old_value) %>% + officer::body_replace_all_text( + old_value = old_value, + new_value = new_value, + only_at_cursor = TRUE, fixed = TRUE + ) + return(doc_updated) +} + +#' Find and delete output in Word doc +#' @description After escaping the output title or caption, it creates a regular +#' expression to find it in the text. If no occurrence is provided, it searches +#' using 'cursor_reach()'. If occurrence sis provided uses the utils function +#' 'cursor_reach_list()' +#' +#' @param template_docx Docx document to be modified +#' @param output_title Title of the output to search for +#' @param output_type Type (Table or Figure) +#' @param occurrence Int. Optional, when title is repeated is used to order them +#' +#' @return doc_updated +#' +#' @import checkmate +#' @import officer +#' @importFrom magrittr `%>%` +#' +#' @examples +#' \dontrun{ +#' find_and_delete_output(doc, +#' "Number of subjects", +#' "tbl", +#' 1) +#' } + +find_and_delete_output = function(template_docx, + output_title, + output_type, + occurrence){ + + checkmate::assert_choice(output_type, + choices = c("TBL", "tbl", "Tbl", "FIG", "fig", "Fig") + ) + + output_title_escaped <- escape_caption(output_title) + + # Create regex depending on Table or Figure + if (toupper(output_type) == "TBL") { + regex_str <- paste0('^(Table).*[0-9]+.*', output_title_escaped, '$') + } else if (toupper(output_type) == "FIG") { + regex_str<- paste0('^(Figure).*[0-9]+.*', output_title_escaped, '$') + } + # If occurrence param is NA/NULL, means that caption should be unique, so we + # look for it normally. This is because if there is more than one output with + # the same caption, cursor_Reach() always ends up at the first one (as it always + # starts looking from the beginning) + if (is.null(occurrence) || is.na(occurrence)) { + doc_updated <- template_docx %>% + officer::cursor_reach(regex_str) %>% + officer::cursor_forward() %>% + officer::body_remove() + } else { + # If not, that means that captions are not unique + # Using cursor_reach_list, we get a list of positions that matches the regex, + # that is ordered. + matches <- cursor_reach_list(template_docx, regex_str) + # We use occurrence, to extract the 1st, 2nd, 3rd... occurrence, and move the + # cursor to that position + template_docx$officer_cursor$which <- matches$positions[occurrence] + # On that position, we remove the Table/Figure + doc_updated <- template_docx %>% + officer::cursor_forward() %>% + officer::body_remove() + } + + return(doc_updated) +} + + +#' Add Generic Report Style to a flextable +#' +#' @param flex_table A flextable to modify style +#' @param widths If there is a specified width for the columns +#' +#' @return Flextable with Generic Report Style +#' +#' @import flextable +#' @importFrom magrittr `%>%` +#' +#' @examples +#' \dontrun{ +#' table_styling(table, widths = NA) +#' } +table_styling <- function(flex_table, + widths = NA) { + widths <- eval(parse(text = widths)) + + flextable::set_flextable_defaults( + font.size = 10, + font.family = "Helvetica", + border.color = "black", + text.align = "left" + ) + + + flex_table <- flex_table %>% + flextable::bg(i = 1, bg = "#b9dba4", part = "header") %>% # Background color for the header row (Accent 3) + flextable::color(i = 1, color = "black", part = "header") %>% # White text for the header + flextable::bold(i = 1, bold = TRUE, part = "header") %>% # Bold text in the header + flextable::align(i = 1, align = "left", part = "header") %>% # Left align the header text + flextable::border(border = fp_border(color = "#d3d3d3"), part = "all") %>% # Black gridlines around all cells + flextable::align(align = "left", part = "body") %>% # Center align body text + flextable::fontsize(i=1, size=12, part='header') %>% + flextable::fontsize(size=10, part='body') + + if (length(widths) == 1) { # table with equal widths + flex_table <- flex_table %>% flextable::width(width = rep(6.3 / ncol_keys(flex_table), ncol_keys(flex_table))) + } else { # manual widths in proportions + if (length(widths) != ncol_keys(flex_table)) { + cat(flex_table) + cat(length(widths)) + cat(ncol_keys(flex_table)) + stop("The number of 'widths' does not match the number of columns in the table.") + } + for (i in 1:length(widths)) { + flex_table <- flex_table %>% flextable::width(j = i, width = 6.3 * widths[i]) + } + } + return(flex_table) +} + +#' Escape special characters in a string for regular expresion +#' @description This function escapes special characters in a given caption +#' string using the gsub function. Special characters can cause issues in certain +#' contexts, such as regular expressions or when processing text data. By escaping +#' these characters, the resulting string can be safely used in various applications. +#' +#' @param caption Caption/Title of a figure +#' +#' @details +#' The function uses the gsub function to replace special characters in the +#' input caption with their escaped versions. The regular expression pattern includes +#' a character class specifying the set of characters to be escaped. +#' The replacement string contains four backslashes followed by the matched special +#' character to ensure proper escaping. +#' +#' @return escaped caption +#' +#' @examples +#' \dontrun{ +#' caption <- "This is a figure title (an a parenthesis use!)" +#' escape_caption(caption) +#' } +escape_caption <- function(caption) { + caption_escaped <- gsub("([-&*@{}^:=!/()%+?;'~|\\]|\\[|\\])", "\\\\\\1", caption) + return(caption_escaped) +} + + +#' Change table in a docx +#' +#' @param template_doc Doc to modify +#' @param table Table to be added +#' @param widths If there are any pre-specified widths for columns in table +#' @param full_path Path tot eh folder where the csv is +#' +#' @return Updated document with new table +#' @import flextable +#' @import utils +#' @import stats +#' @importFrom magrittr `%>%` +#' +#' @examples +#' \dontrun{ +#' change_table(doc, "data.csv", widths = NA) +#' } +change_table <- function(template_doc, + full_path, + table, + widths = NA) { + col_names <- utils::read.csv(file.path(full_path, table), header = FALSE, nrows = 1) + + flex_table <- utils::read.csv(file.path(full_path, table), header = FALSE, skip = 1) %>% + stats::setNames(col_names) %>% + flextable::flextable() + + if (is.null(widths)) { + widths <- NA + } + + flex_table <- table_styling(flex_table = flex_table, widths = widths) + + doc_updated <- template_doc %>% + flextable::body_add_flextable(flex_table, pos='before') + + return(doc_updated) +} + +#' Change figure in a docx +#' +#' @param template_doc Doc to modify +#' @param figure Figure to be added +#' @param full_path Path to the folder where the figrue is +#' +#' @return Updated document with new figure +#' +#' @import png +#' @import jpeg +#' @import officer +#' @import tools +#' @import magick +#' @importFrom magrittr `%>%` +#' +#' @examples +#' \dontrun{ +#' change_figure(doc, "/path/to/output", "output.png") +#' } +change_figure <- function(template_doc, + full_path, + figure) { + + image_ext <- tools::file_ext(figure) + + if (image_ext == "png") { + fig <- png::readPNG(file.path(full_path, figure)) + dimensions <- dim(fig) + } else if ((image_ext == "jpeg") | (image_ext == "jpg")) { + fig <- jpeg::readJPEG(file.path(full_path, figure)) + dimensions <- dim(fig) + } else if (image_ext == "bmp") { + fig <- magick::image_read(file.path(full_path, figure)) + fig_info <- magick::image_info(fig) + dimensions <- c(fig_info$width, fig_info$height) + } else if (image_ext == "pdf") { + fig <- magick::image_read_pdf(file.path(full_path, figure))[1] + fig_info <- magick::image_info(fig) + dimensions <- c(fig_info$width, fig_info$height) + } + + doc_updated <- template_doc %>% + officer::body_add_img( + src = file.path(full_path, figure), + pos = "before", + width = 6.3, + height = 6.3 * dimensions[1] / dimensions[2] + ) + + return(doc_updated) +} + +#' Save updated document +#' +#' @param document Officer doc to be saved +#' @param doc_final_path Location to save +#' +#' @import officer +#' +#' @examples +#' \dontrun{ +#' save_updated_document(doc, "./new_doc.docx") +#' } +save_updated_document <- function(document, + doc_final_path) { + print(document, target = doc_final_path) +} + + +#' Version number +#' +#' @param version Number. The current version of the document +#' +#' @return formatted version "_v00", "_v02", _v13" +#' +#' @import checkmate +#' +#' @examples +#' \dontrun{ +#' version_number(5) +#' } +version_number <- function(version) { + checkmate::assert_int(version, lower = 0, upper = 99) + + # Format the number with leading zeros and create the string + formatted_version <- sprintf("%02d", version) + formatted_version <- paste0("_v", formatted_version) + + return(formatted_version) +} + + +#' Cursor reach list +#' @description This function read a word document with xml2, looks for a pattern +#' in the text (i.e Figure/tables captions)/ Return the positions and text for this +#' search, and also possible sources path underneath the placeholder figure/tables +#' +#' @param x Document to be searched, already read by officer +#' @param keyword String. String to be found in the docx document +#' +#' @return List containing the positions where the keyword was found and the +#' corresponding text associated, as well as the positions where the possible source +#' path could be and its associated source path text +#' +#' @import xml2 +#' +#' @examples +#' \dontrun{ +#' cursor_reach_list(doc, "example caption") +#' } +cursor_reach_list <- function(x, keyword) { + # everything was inspired by officer's cursor_reach function + # https://stackoverflow.com/questions/75743350/cursor-location-for-multiple-matches-officer-package-in-r + + # Get the nodes with text: searches like a path in document/body document/footer + # and document/header + nodes_with_text <- xml_find_all(x$doc_obj$get(), "/w:document/w:body/*|/w:ftr/*|/w:hdr/*") + if (length(nodes_with_text) < 1) { + stop("no text found in the document", call. = FALSE) + } + + # get text and find the pattern we are looking for + text_ <- xml2::xml_text(nodes_with_text) + test_ <- grepl(pattern = keyword, x = text_) + if (!any(test_)) { + stop(keyword, " has not been found in the document", + call. = FALSE) + } + # Get positions where it is a match and the associated text + positions <- which(test_) + text_fig_table_ <- text_[positions] + + # Look for sources under the Figure/Table. We add +2 to the positions because + # in our use case, to reach the Source line, will have to move 2 positions: from + # the caption to the placeholder figrue/table, and from the placeholder to the + # source line + positions_source <- positions + 2 + text_source_ <- text_[positions_source] + + # As not all the figure/table might have a source path, we filter for the ones + # that start with "Source:" + source_pattern <- "^Source:\\s*(.*)" + test_source_ <- grepl(pattern = source_pattern, x = text_source_) + # Keep the sources, and put a NA in the non-sources + non_sources <- which(!test_source_) + text_source_[non_sources] <- NA + # Remove the "Source:" part of the string so that we keep only the path + text_source_ <- sub(source_pattern, "\\1", text_source_) + + results <- list("positions" = positions, + "text" = text_fig_table_, + "positions_source" = positions_source, + "text_source"= text_source_) + + return(results) +} + + +#' Search for duplicates +#' @description Look for duplicates in captions titles and raise a warning if +#' the occurrence parameter is not defined. +#' +#' @param yaml_file List. Already read yaml file to be checked for duplicates +#' @import dplyr +#' +search_for_duplicates = function(yaml_file) { + + df <- data.frame(title = character(), occurrence = numeric()) + + for (entry in yaml_file) { + # get title and occurrence + title <- entry$title + if ("occurrence" %in% names(entry) && !is.null(entry$occurrence)) { + occurrence <- entry$occurrence + } else { + occurrence <- NA + } + # add to the df + df <- rbind(df, data.frame(title = title, occurrence = occurrence)) + } + # Check for duplications in title parameter + duplicated_titles <- df %>% + dplyr::count(title) %>% + dplyr::filter(n > 1) %>% + dplyr::pull(title) + + # if repeated check that occurrence is not NA + for (tit in duplicated_titles) { + dupes <- df %>% + dplyr::filter(title == tit) + if (any(is.na(dupes$occurrence))) { + warning(paste("Duplicated title '", tit, "' missing occurrence parameter.\nConsider making the titles unique as unexpected behaviour can be encountered. If not, make sure to have added the param 'occurrence' in the yaml file")) + } + } +} + + +#' Extract figure and table captions +#' @description given a word document, extract all the caption for figures and +#' tables using 'cursor_reach_list()'. If the docx has a ToC, 'cursor_reach_list()' +#' will return captions from both the body of the text and the ToC. For that, +#' 'extract_figure_table_captions()' cleans the captions based on keywords +#' contained internally in the docx. ToC captions (keyword PAGEREF) but the first +#' caption one is always missed by the searching function. Body captions +#' (keyword STYLEREF or nothing) are always found correctly. +#' Therefore, if STYLEREF is present we filter for those. If there is no STYLEREF +#' but there is PAGEREF, we take the ones that are not PAGEREF. If not STYLEREF +#' or PAGEREF, no need to clean anything. +#' +#' @param doc_path String. Path to the docx document +#' +#' @import officer +#' @import stringr +#' +#' @return List figure_table_captions_sources contaning the figure_table_captions +#' and its corresponding sources paths +#' +#' @examples +#' \dontrun{ +#' extract_figure_table_captions(system.file("use_cases/02_automated_reporting", +#' "Automated_Reporting_Example.docx", +#' package="rdocx")) +#' } +extract_figure_table_captions = function(doc_path) { + # Read docx + doc <- officer::read_docx(doc_path) + + # Define regex for search + regex_str <- "^(Figure|Table).*[0-9]+.*$" + + # Look for the regex in the document and extract positions matching + matches <- cursor_reach_list(doc, regex_str) + + # Extract the matching expressions + figure_table_captions <- matches$text + sources_paths <- matches$text_source + + if (any(grepl("STYLEREF", figure_table_captions))) { + # First, if all the formatting is still untouched, the good captions should have + # the word STYLEREF + pos_ <- grepl("STYLEREF", figure_table_captions) + pos <- which(pos_) + figure_table_captions <- figure_table_captions[pos] + # Keep the associated sources with this captions + sources_paths <- sources_paths[pos_] + } else if (any(grepl("PAGEREF", figure_table_captions))){ + # It could happen then that for some reason, the format and styles have been + # lost, so we would like to keep the captions from the Figures/Tables on + # the text, not the ones from the ToC + pos_ <- grepl("PAGEREF", figure_table_captions) + pos <- which(!pos_) + figure_table_captions <- figure_table_captions[pos] + # Keep the associated sources with this captions + sources_paths <- sources_paths[pos] + } + + figure_table_captions_sources <- list("figure_table_captions" = figure_table_captions, + "sources_paths" = sources_paths) + + + return(figure_table_captions_sources) +} + +#' Remove Figure/Table part of caption +#' @description Used to remove the first part fo the captions. It will clean +#' normal captions like Figure/Table followed by its numbering (i.e. Figure 1-1, +#' Table 2-1), and also more complex patterns created by using Novstyle such as +#'"Table STYLEREF 1 \\s 2 SEQ Table \\* ARABIC \\s 1 1. Summary of Sample Statistics" +#' +#' @param caption String. Caption that need to be modified +#' +#' @return updated caption without Figure or Table number +#' +#' @examples +#' \dontrun{ +#' example_caption <- +#' "Table STYLEREF 1 \\s 2 SEQ Table \\* ARABIC \\s 1 1. Summary of Sample Statistics" +#' updated_example_caption <- remove_fig_table_part(example_caption) +#' expected_output <- "Summary of Sample Statistics" +#' } +remove_fig_table_part = function(caption) { + # If the caption contains MERGEFORMAT, then we clean with the more complex regex + # and if not we asume that ther eis not underlying compliacted pattern and clean + # normally. + if (any(grepl("STYLEREF", caption))) { + # Regex that removes a string starting with Figure or Table and that contains + # a MERGEFORMAT string followed by another MERGEFORMAT and number. + pattern <- "^(Figure|Table)\\s+STYLEREF.+?\\d+\\.\\s*" + updated_caption <- sub(pattern, "", caption) + # In case there is an extra white space in front of the string + updated_caption <- sub("^\\s+", "", updated_caption) + } else { + # Regex that removes a string starting with Figure or Table followed by a space + # and number hypen and number. + pattern <- "(Figure|Table)\\s*([0-9]+[-]*[0-9]*)\\s*" + updated_caption <- sub(pattern, "", caption) + } + return(updated_caption) +} + + +#' Create caption yaml +#' @description Given a list of captions, it creates a template yaml file with +# a predefined structure and parameters to be set: +#' +#' - type. Indicates if it is a table (TBL) or a figure (FIG) +#' +#' - title. The title or caption of the table/figure in the template docx file +#' +#' - file. The name of the file (csv or image) to be used in the new version of the docx +#' +#' - widths. For tables only, a pre-defined column widths (useful for model parameter table, for example) +#' An example of the yaml structure: +#' +#' - occurrence. Int. To be used when captions are duplicated for more than one +#' figure/table. To set the order they appear in the word document. +#' +#' \preformatted{ +#' output_1: +#' type: TBL +#' title: "Caption of the output 1" +#' file: ~ +#' widths: ~ +#' occurrence: ~ + +#' output_2: +#' type: FIG +#' title: "Caption of the output 2" +#' file: ~ +#' widths: ~ +#' occurrence: ~ +#' } +#' +#' +#' @param figure_table_captions_sources List of the tables and figures with its captions +#' @param yaml_filename String. Output yaml filename, e.g. "/path/to/file/filename.yml" +#' +#' @import yaml +#' @import stringr +#' +#' @examples +#' \dontrun{ +#' captions <- extract_figure_table_captions(system.file("use_cases/02_automated_reporting", +#' "Automated_Reporting_Example.docx", +#' package="rdocx")) +#' create_yml(captions, "./example_yml.docx") +#' } +create_yml = function(figure_table_captions_sources, + yaml_filename) { + + yaml_output <- list() + + figure_table_captions <- figure_table_captions_sources$figure_table_captions + sources_paths <- figure_table_captions_sources$sources_paths + + for (i in seq_along(figure_table_captions)) { + entry <- list( + type = ifelse(stringr::str_detect(figure_table_captions[[i]], "^Figure"), "FIG", "TBL"), + title = toString(remove_fig_table_part(figure_table_captions[[i]])), + file = if (!is.na(sources_paths[[i]])) sources_paths[[i]] else NULL, + widths = NULL, + occurrence = NULL + ) + entry_name <- paste0("output_", i) + + yaml_output[[entry_name]] <- entry + } + yaml::write_yaml(yaml_output, yaml_filename) +} diff --git a/R/utils_checks.R b/R/utils_checks.R new file mode 100644 index 0000000..78567ca --- /dev/null +++ b/R/utils_checks.R @@ -0,0 +1,89 @@ +# Copyright (c) 2023-2024 Novartis, rdocx authors +# +# This file is part of rdocx. +# +# Licensed under the MIT license: +# +# http://www.opensource.org/licenses/mit-license.php +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +#' @title Check date provided +#' @description Check to see if a string is a date format +#' +#' @param date_str String. Candidate string to check. +#' @param date_format String. Expected date format. Default is dd-Mmm-yyyy. +#' +#' @return the original date str if matches conditions, FALSE if not +#' +#' @examples +#' \dontrun{ +#' check_string_is_date("01-Oct-2023") +#' } +check_string_is_date <- function(date_str, date_format = "%d-%b-%Y") { + + if (!is.na(as.Date(date_str, format = date_format))) { + return(date_str) + } else { + stop(paste0("`Date` was not provided in the expected format (", date_format, ").", + " For example: 01-Oct-2023")) + } +} + + +#' @title Check name provided +#' @description Check to see if name has Firstname Lastname and provide a custom +#' error to guide users +#' @param name_str Name to be checked +#' +#' @return the original date str if matches conditions, FALSE if not +#' @import checkmate +#' +#' @examples +#' \dontrun{ +#' check_name("Harry Styles") +#' } +check_name <- function(name_str){ + checkmate::expect_string(name_str, + pattern = ' ', + info = "`name` does not conform to the format + \'Firstname Lastname\'") + return(name_str) +} + +#' @title Check for NULL or flextable +#' @description Checks if the objects passed is either NULL or a flextable +#' @param variable object to check +#' +#' @return the variable if matches conditions, error if not +#' +#' @examples +#' \dontrun{ +#' ft <- flextable::flextable(head(mtcars)) +#' check_null_or_flextable(ft) +#' } +check_null_or_flextable <- function(variable) { + + if (is.null(variable) || inherits(variable, "flextable")) { + return(variable) + } else { + stop("If you want to add a Changelog Table, it needs to be a flextable, the + result of using ChangelofTable$get_Table()") + } +} diff --git a/README.Rmd b/README.Rmd new file mode 100644 index 0000000..993408c --- /dev/null +++ b/README.Rmd @@ -0,0 +1,83 @@ +--- +output: github_document +--- + + + +```{r, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>", + fig.path = "man/figures/README-", + out.width = "100%" +) +``` + +# rdocx + + +[![Codecov test coverage](https://codecov.io/gh/ines-gimeno-molina/R-test/branch/main/graph/badge.svg)](https://app.codecov.io/gh/ines-gimeno-molina/R-test/?branch=main) +[![R-CMD-check.yaml](https://github.com/ines-gimeno-molina/R-test/actions/workflows/check-package.yml/badge.svg)](https://github.com/ines-gimeno-molina/R-test/actions/workflows/check-package.yml) + + + +## Introduction + +In clinical trials we work in interdisciplinary teams where the discussion of outputs is often facilitated using static documents. We wanted to bring the advantages of modern tools (R, markdown, git) and software development practices to the production of company documents. We used an object-oriented approach to create classes for report items with a suite of tests. Finally, the report is rendered programmatically in docx format using a company template. This enables our statisticians to work in a truly end to end fashion within a GxP environment with the end product in a format suitable for interdisciplinary collaboration. + +For the open-source version of our package we have replaced our company template/s with a generic template that demonstrates the functionality of the package. In most cases, you prefer to provide a custom template to enforce your desired formatting. In that case, please ensure that the classes are compatible with your template and adjust as needed. + +## The use cases + +As for now, there are two main applications of **{rdocx}**: + +- [Generic Report](ADD LINK): As our first use case, the Generic Report Rmd template allows the user to create a report in RMarkdown and render it directly from R into a Word document following the style of the Generic Report Word template that is used as a reference document by the Generic Report Rmd template. + +- [Automated Reporting](ADD LINK): Allows the user to automatically update all or selected tables/figures in a Word document. + +## Installation + +**{rdocx}** can be installed from Gitlab using (update before pushing to Github) + +```{r, eval=FALSE} +devtools::install_github( + host = "", + repo = "", + lib = "~/R", + build_vignettes = TRUE +) +``` + +Remember to set the parameter `lib` to the desired location for installation, as +it will be installed in your local directory. + +## Usage + +```{r, eval=FALSE} +library('rdocx', lib.loc="~/R") +``` + +For quick access to the documentation, visit [ADD LINK TO PAGES IN GITHUB]. Read the vignettes on how to use the package with step-by-step examples online: + +- [Generic Report](ADD LINK) + +- [Automated Reporting](ADD LINK) + + +Or call the vignettes directly in R: + +```{r, eval=FALSE} +vignette("generic_report", package = "rdocx") +``` + +```{r, eval=FALSE} +vignette("automated_reporting", package = "rdocx") +``` + + +## Class Structure Overview + +The structure of the package is displayed in a simplified format below. Note that +further helper and checking functions exist that are not visualised here for simplicity. + +![](https://mermaid.ink/img/pako:eNqtlVFr2zAQx7-K0dPG2n6A0JeywR432j4axEW-2AJZ8qRTXVPy3SdbTuLIclhhDgRb99Pp_n-d7A8mTIVsx4QC535IqC20pS7C9RM1WimesTOWisf7--JVksLfUOMW8L0BXaMy9Svs1Sb1JEi-SRpuQi-y1kDeoltgT55MC4RVBKWuJzY8KRD4y1PnyRWRva6leHx4SIaeTR_JZKkJTcbObPyfzEqK_oihY6mXTKbiC5gmPPt7YsbrmyM7TrNTAk4js45W2IGlFjWtY458NWxNjEHt2z3aXDSY4Nbjb2idNDpTR9C6Ht17xyeJi9BB4TtNfovTtnC6bHXCuPN-rKAaZ1d4F6z78nXT3qQjlh5DVXFr-tPkS96R_OeUoUtyO5f3ZNPCvhm47_KT-gaIR7-qRVRjvyhyWWDa2_9D9Pps5FRraD_Zp4nka1FpEVcvkdz6MAPrSAtSc_DUGMvzVUZiTsDzW_FH3M4xxjcy3DZ5qTJ5s2VkErYBIeSVEe_8IBXmyzExRTgj1GSsNyLM1aBuZBjabHQUI6Cj0MvhbBoesKuOiq3MQSk-17BSPf7YHWvRBuOr8DGadJaMGgxLsV24rfAAXlHJSn0MaHDevAxasB1Zj3fMGl83bHcA5cJTXHL-mEXk-BeGPjNY?type=png) diff --git a/README.md b/README.md index c806d9d..4d8ede5 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,91 @@ + + + # rdocx -Create and edit docx files from R + + + +[![Codecov test +coverage](https://codecov.io/gh/ines-gimeno-molina/R-test/branch/main/graph/badge.svg)](https://app.codecov.io/gh/ines-gimeno-molina/R-test/?branch=main) +[![R-CMD-check.yaml](https://github.com/ines-gimeno-molina/R-test/actions/workflows/check-package.yml/badge.svg)](https://github.com/ines-gimeno-molina/R-test/actions/workflows/check-package.yml) + + +## Introduction + +In clinical trials we work in interdisciplinary teams where the +discussion of outputs is often facilitated using static documents. We +wanted to bring the advantages of modern tools (R, markdown, git) and +software development practices to the production of company documents. +We used an object-oriented approach to create classes for report items +with a suite of tests. Finally, the report is rendered programmatically +in docx format using a company template. This enables our statisticians +to work in a truly end to end fashion within a GxP environment with the +end product in a format suitable for interdisciplinary collaboration. + +For the open-source version of our package we have replaced our company +template/s with a generic template that demonstrates the functionality +of the package. In most cases, you prefer to provide a custom template +to enforce your desired formatting. In that case, please ensure that the +classes are compatible with your template and adjust as needed. + +## The use cases + +As for now, there are two main applications of **{rdocx}**: + +- [Generic Report](ADD%20LINK): As our first use case, the Generic + Report Rmd template allows the user to create a report in RMarkdown + and render it directly from R into a Word document following the style + of the Generic Report Word template that is used as a reference + document by the Generic Report Rmd template. + +- [Automated Reporting](ADD%20LINK): Allows the user to automatically + update all or selected tables/figures in a Word document. + +## Installation + +**{rdocx}** can be installed from Gitlab using (update before pushing to +Github) + +``` r +devtools::install_github( + host = "", + repo = "", + lib = "~/R", + build_vignettes = TRUE +) +``` + +Remember to set the parameter `lib` to the desired location for +installation, as it will be installed in your local directory. + +## Usage + +``` r +library('rdocx', lib.loc="~/R") +``` + +For quick access to the documentation, visit \[ADD LINK TO PAGES IN +GITHUB\]. Read the vignettes on how to use the package with step-by-step +examples online: + +- [Generic Report](ADD%20LINK) + +- [Automated Reporting](ADD%20LINK) + +Or call the vignettes directly in R: + +``` r +vignette("generic_report", package = "rdocx") +``` + +``` r +vignette("automated_reporting", package = "rdocx") +``` + +## Class Structure Overview + +The structure of the package is displayed in a simplified format below. +Note that further helper and checking functions exist that are not +visualised here for simplicity. + +![](https://mermaid.ink/img/pako:eNqtlVFr2zAQx7-K0dPG2n6A0JeywR432j4axEW-2AJZ8qRTXVPy3SdbTuLIclhhDgRb99Pp_n-d7A8mTIVsx4QC535IqC20pS7C9RM1WimesTOWisf7--JVksLfUOMW8L0BXaMy9Svs1Sb1JEi-SRpuQi-y1kDeoltgT55MC4RVBKWuJzY8KRD4y1PnyRWRva6leHx4SIaeTR_JZKkJTcbObPyfzEqK_oihY6mXTKbiC5gmPPt7YsbrmyM7TrNTAk4js45W2IGlFjWtY458NWxNjEHt2z3aXDSY4Nbjb2idNDpTR9C6Ht17xyeJi9BB4TtNfovTtnC6bHXCuPN-rKAaZ1d4F6z78nXT3qQjlh5DVXFr-tPkS96R_OeUoUtyO5f3ZNPCvhm47_KT-gaIR7-qRVRjvyhyWWDa2_9D9Pps5FRraD_Zp4nka1FpEVcvkdz6MAPrSAtSc_DUGMvzVUZiTsDzW_FH3M4xxjcy3DZ5qTJ5s2VkErYBIeSVEe_8IBXmyzExRTgj1GSsNyLM1aBuZBjabHQUI6Cj0MvhbBoesKuOiq3MQSk-17BSPf7YHWvRBuOr8DGadJaMGgxLsV24rfAAXlHJSn0MaHDevAxasB1Zj3fMGl83bHcA5cJTXHL-mEXk-BeGPjNY?type=png) diff --git a/_pkgdown.yml b/_pkgdown.yml new file mode 100644 index 0000000..b7517c2 --- /dev/null +++ b/_pkgdown.yml @@ -0,0 +1,3 @@ +url: ~ +template: + bootstrap: 5 diff --git a/inst/use_cases/.DS_Store b/inst/use_cases/.DS_Store new file mode 100644 index 0000000..1957c59 Binary files /dev/null and b/inst/use_cases/.DS_Store differ diff --git a/inst/use_cases/00_word_templates/.DS_Store b/inst/use_cases/00_word_templates/.DS_Store new file mode 100644 index 0000000..8959654 Binary files /dev/null and b/inst/use_cases/00_word_templates/.DS_Store differ diff --git a/inst/use_cases/00_word_templates/generic_report/.DS_Store b/inst/use_cases/00_word_templates/generic_report/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/inst/use_cases/00_word_templates/generic_report/.DS_Store differ diff --git a/inst/use_cases/00_word_templates/generic_report/Generic_Report_Template.docx b/inst/use_cases/00_word_templates/generic_report/Generic_Report_Template.docx new file mode 100644 index 0000000..2f5e520 Binary files /dev/null and b/inst/use_cases/00_word_templates/generic_report/Generic_Report_Template.docx differ diff --git a/inst/use_cases/01_generic_report/generic_report_template.Rmd b/inst/use_cases/01_generic_report/generic_report_template.Rmd new file mode 100644 index 0000000..7339dcc --- /dev/null +++ b/inst/use_cases/01_generic_report/generic_report_template.Rmd @@ -0,0 +1,267 @@ +--- +output: + word_document: + toc: yes +toc-title: Table of contents +--- + + +```{r, header, include=F} + +# Install rdocx and load it +library(rdocx) + +# Load other required R packages +library(officer) +library(flextable) +library(dplyr) +library(kableExtra) +library(R6) +library(lgr) + +# Load R packages for the example plot +library(ggplot2) +library(cowplot) + + +# These are some examples of commands to set the global options for knitr Rmd compiler. +knitr::opts_chunk$set(echo = FALSE) +knitr::opts_chunk$set(message = FALSE) +knitr::opts_chunk$set(warning = FALSE) +knitr::opts_chunk$set(cache = FALSE) +knitr::opts_chunk$set(tab.cap.pre = "", tab.cap.sep = "") + +# Add your custom functions and settings below (if any) + +``` + + +```{r, titlepage-info} +# === Provide information for the title page === + +# This information will be displayed on the title page of the rendered +# document. +tp <- TitlePage$new( + report_title= "Super cool document", + department = "Cool department", + study_title = "Compute super cool things", + study_number = "OWNDJQW9923", + status = "Draft", + version = "1", + date = "01-Feb-2024", + bus_class = "Confidential") + +``` + +```{r, changelog-info} +# === Provide information for the change log table === + +# This information will be displayed on the second page of the rendered +# document. You need to add as many ChangelogTableRow elements as rows as you need in your +# change log table. After that, you'll need to assign these ChangelogTableRow elements +# to a ChangelogTable element. + +ctr_1 <- ChangelogTableRow$new( + date = '01-Oct-2024', + version = 'Initial version', + why_update = 'Create first version of the document', + what_changed = 'NA/ Initial version') + +ctr_2 <- ChangelogTableRow$new( + date = '15-Oct-2024', + version = 'Final version', + why_update = 'Change calculation of param 1', + what_changed = 'Section 2') + +changelog_table <- ChangelogTable$new() +changelog_table$add_row(ctr_1) +changelog_table$add_row(ctr_2) + +``` + +```{r} +# === Provide information for the signatures table === + +# This information will be displayed on the third page of the rendered +# document. You need to add as many SignaturesTableRow elements as rows as you need in your +# signatures table. After that, you'll need to assign these SignaturesTableRow elements +# to a SignaturesTable element. + +str <- SignaturesTableRow$new( + name = 'Harry Styles', + department = 'eCompliance', + date = '15-Feb-2024') + +str_2 <- SignaturesTableRow$new( + name = 'Alex Turner', + department = 'Product Development', + date = '20-Feb-2024') + +signatures_table <- SignaturesTable$new() +signatures_table$add_row(str) +signatures_table$add_row(str_2) + + +``` + + +```{r, final-title-pages} +# === This code chunk generates the title page (do not modify) === + +# Append change log table and signatures table to the title page +tp$changelog_table <- changelog_table$get_table() +tp$signatures_table <- signatures_table$get_table() +tp$get_title_page() + +``` + +\newpage + +# Activity Methodology + +  + +__Introduction__ + +Start adding your text here. Example text: This document serves as evidence of the accuracy of the information provided in the study protocol OWNDJQW9923 v1 Section X.Y. The calculations and review [or reassessment] were performed by the following statisticians: + + +```{r statistician-names} +# === Provide information for the Activity Table === + +at <- ActivityTable$new( + activity = 'Super cool calculation', + main_author_name = 'David Bowie', + main_activity_date = '01-Oct-2024', + qc_author_name = 'Freddie Mercury', + qc_activity_date = '20-Oct-2024') + +at$get_table() +``` + + +  +__A table__ + +You can directly embed tables into this document (see for example Table 1-2). + +  + +```{r example-table, out.width='\\textwidth'} +# === Example Table 1-2 === + +# Generate some example data +example_data <- data.frame( + column_1 = seq(12), + column_2 = rnorm(12, mean = 10, sd = 1), + column_3 = paste0(round(rbeta(12, 1, 1), 2)*100, "%") +) + +# Make a table using the {flextable} package +example_data %>% + flextable::flextable() %>% + flextable::width(width = 2) %>% + # Make headers bold + flextable::bold(bold = TRUE, part = "header") %>% + # Use zebra theme for row colors + flextable::theme_zebra() %>% + # Define column names + flextable::set_header_labels( + column_1 = "Header 1", + column_2 = "Header 2", + column_3 = "Header 3" + ) %>% + # Set borders + flextable::border_outer() %>% + flextable::border_inner_h() %>% + flextable::border_inner_v() %>% + # Add table captions + flextable::set_caption( + caption = "Table 1-2: This is an example table. Add caption text here.", + style = "Table Caption", autonum = "autonum" + ) +``` + +  +__A figure__ + +You can also embed figures (see for example Figure 1-1). + +```{r example-figure, fig.dim = c(6, 2.5), fig.cap="Figure 1-1: Add your figure caption here.", fig.crop=F} +# === Example Figure 1-1 === + +# * Adjust figure dimensions using the fig.dim option in the chunk settings +# above: fig.dim = (width in inches, length in inches) +# * Add the figure caption using the fig.cap option in the chunk settings +# above: fig.cap = "Add your caption text here". + +# Plot ToothGrowth data set using {ggplot2} +ggplot(ToothGrowth) + + geom_boxplot( + aes(dose, len, color = supp, group = interaction(dose, supp)), + width = 0.2, + outlier.size = 0.5, + position = position_dodge(width = 0.4) + ) + + geom_point( + aes(dose, len, color = supp), + position = position_dodge(width = 0.4), + alpha = 0.7, + size = 0.5 + ) + + theme_minimal(base_size = 12) + + cowplot::panel_border() + + theme(text = element_text(family = "serif")) + + scale_color_brewer( + name = "Legend title", + palette = "Dark2" + ) + + labs( + x = "Title x-axis", + y = "Title y-axis", + title = "Example figure" + ) + +``` + +  + +## This is a subsection + +  + +You can include subsections, add text here. + +  + +### This is a subsubsection + +  + +You can also include subsubsection, add text here. + +  + + + +# References + +  + +Add your references in alphabetical order here (if applicable). For example: + +Einstein, A. (1905). On the movement of small particles suspended in a stationary liquid demanded by the molecular-kinetic theory of heat. Theory of Brownian Movement. + +# Appendix + +## Information for sample size calculation + +  + +Please include additional information as needed. + +## Information for sample size verification + +  + +Please include additional information as needed. diff --git a/inst/use_cases/02_automated_reporting/Automated_Reporting_Example.docx b/inst/use_cases/02_automated_reporting/Automated_Reporting_Example.docx new file mode 100644 index 0000000..256942a Binary files /dev/null and b/inst/use_cases/02_automated_reporting/Automated_Reporting_Example.docx differ diff --git a/inst/use_cases/02_automated_reporting/Automated_Reporting_Example_with_sources.docx b/inst/use_cases/02_automated_reporting/Automated_Reporting_Example_with_sources.docx new file mode 100644 index 0000000..5296206 Binary files /dev/null and b/inst/use_cases/02_automated_reporting/Automated_Reporting_Example_with_sources.docx differ diff --git a/inst/use_cases/02_automated_reporting/example_outputs/Figure_2_1_Data_Point_Distribution.png b/inst/use_cases/02_automated_reporting/example_outputs/Figure_2_1_Data_Point_Distribution.png new file mode 100644 index 0000000..35e49fa Binary files /dev/null and b/inst/use_cases/02_automated_reporting/example_outputs/Figure_2_1_Data_Point_Distribution.png differ diff --git a/inst/use_cases/02_automated_reporting/example_outputs/Figure_2_2_Comparative_Analysis.png b/inst/use_cases/02_automated_reporting/example_outputs/Figure_2_2_Comparative_Analysis.png new file mode 100644 index 0000000..5a0aafb Binary files /dev/null and b/inst/use_cases/02_automated_reporting/example_outputs/Figure_2_2_Comparative_Analysis.png differ diff --git a/inst/use_cases/02_automated_reporting/example_outputs/Figure_2_3_Comparative_Analysis.png b/inst/use_cases/02_automated_reporting/example_outputs/Figure_2_3_Comparative_Analysis.png new file mode 100644 index 0000000..84db4e8 Binary files /dev/null and b/inst/use_cases/02_automated_reporting/example_outputs/Figure_2_3_Comparative_Analysis.png differ diff --git a/inst/use_cases/02_automated_reporting/example_outputs/Figure_2_4_Trend_Analysis.png b/inst/use_cases/02_automated_reporting/example_outputs/Figure_2_4_Trend_Analysis.png new file mode 100644 index 0000000..fe0c15b Binary files /dev/null and b/inst/use_cases/02_automated_reporting/example_outputs/Figure_2_4_Trend_Analysis.png differ diff --git a/inst/use_cases/02_automated_reporting/example_outputs/Figure_2_5_Trend_Analysis.png b/inst/use_cases/02_automated_reporting/example_outputs/Figure_2_5_Trend_Analysis.png new file mode 100644 index 0000000..2594128 Binary files /dev/null and b/inst/use_cases/02_automated_reporting/example_outputs/Figure_2_5_Trend_Analysis.png differ diff --git a/inst/use_cases/02_automated_reporting/example_outputs/Figure_3_1_Trend_Analysis.png b/inst/use_cases/02_automated_reporting/example_outputs/Figure_3_1_Trend_Analysis.png new file mode 100644 index 0000000..93f57e9 Binary files /dev/null and b/inst/use_cases/02_automated_reporting/example_outputs/Figure_3_1_Trend_Analysis.png differ diff --git a/inst/use_cases/02_automated_reporting/example_outputs/Table_2_1_Sample_Statistics.csv b/inst/use_cases/02_automated_reporting/example_outputs/Table_2_1_Sample_Statistics.csv new file mode 100644 index 0000000..4a1ae95 --- /dev/null +++ b/inst/use_cases/02_automated_reporting/example_outputs/Table_2_1_Sample_Statistics.csv @@ -0,0 +1,6 @@ +"Statistic","Sample_A","Sample_B","Sample_C" +"Mean",0.02,0.04,12.38 +"Median",0.01,0.05,12.37 +"Standard Deviation",0.99,1.01,2.54 +"Min",-2.81,-3.05,8.19 +"Max",3.24,3.39,16.53 diff --git a/inst/use_cases/02_automated_reporting/example_outputs/Table_2_2_Breakdown_of_Category_X.csv b/inst/use_cases/02_automated_reporting/example_outputs/Table_2_2_Breakdown_of_Category_X.csv new file mode 100644 index 0000000..a0aa480 --- /dev/null +++ b/inst/use_cases/02_automated_reporting/example_outputs/Table_2_2_Breakdown_of_Category_X.csv @@ -0,0 +1,4 @@ +"Subcategory","Count","Percentage" +"Sub-X1",10,20 +"Sub-X2",20,40 +"Sub-X3",30,60 diff --git a/inst/use_cases/02_automated_reporting/example_outputs/Table_3_1_Yearly_Performance_Metrics.csv b/inst/use_cases/02_automated_reporting/example_outputs/Table_3_1_Yearly_Performance_Metrics.csv new file mode 100644 index 0000000..a4a94f1 --- /dev/null +++ b/inst/use_cases/02_automated_reporting/example_outputs/Table_3_1_Yearly_Performance_Metrics.csv @@ -0,0 +1,12 @@ +"Year","Metric_A","Metric_B","Metric_C","Metric_D" +2010,84.23,52.44,56.09,71.95 +2011,85.12,68.83,56.64,80.2 +2012,86.34,70.27,62,87.45 +2013,77.47,62.58,53.65,95.7 +2014,77.68,72.7,59.48,87.42 +2015,81.31,56.23,62.99,94.35 +2016,83.15,76.68,69.8,81.49 +2017,78.3,58.36,62.88,87.81 +2018,77.88,54.58,70.29,91.74 +2019,76.91,64.66,62.72,87.12 +2020,87.41,71.13,59.98,90.9 diff --git a/inst/use_cases/02_automated_reporting/example_outputs/example_2_pages.pdf b/inst/use_cases/02_automated_reporting/example_outputs/example_2_pages.pdf new file mode 100644 index 0000000..6596837 Binary files /dev/null and b/inst/use_cases/02_automated_reporting/example_outputs/example_2_pages.pdf differ diff --git a/inst/use_cases/02_automated_reporting/example_outputs/lena.bmp b/inst/use_cases/02_automated_reporting/example_outputs/lena.bmp new file mode 100644 index 0000000..5641e75 Binary files /dev/null and b/inst/use_cases/02_automated_reporting/example_outputs/lena.bmp differ diff --git a/inst/use_cases/02_automated_reporting/example_outputs/pdf_example.pdf b/inst/use_cases/02_automated_reporting/example_outputs/pdf_example.pdf new file mode 100644 index 0000000..5dc1f2e Binary files /dev/null and b/inst/use_cases/02_automated_reporting/example_outputs/pdf_example.pdf differ diff --git a/inst/use_cases/02_automated_reporting/example_yml_1.yml b/inst/use_cases/02_automated_reporting/example_yml_1.yml new file mode 100644 index 0000000..7f77cb3 --- /dev/null +++ b/inst/use_cases/02_automated_reporting/example_yml_1.yml @@ -0,0 +1,54 @@ +# yaml file with all outputs to be updated in example report + +fig_1: + type: FIG + title: "Data Point Distribution in Sample Set A" + file: Figure_2_1_Data_Point_Distribution.png + +fig_2: + type: FIG + title: "Comparative Analysis of Categories X and Y" + file: Figure_2_2_Comparative_Analysis.png + occurrence: 1 + +fig_3: + type: FIG + title: "Comparative Analysis of Categories X and Y" + file: Figure_2_3_Comparative_Analysis.png + occurrence: 2 +fig_4: + type: FIG + title: "Trend Analysis Over Time (Yearly Data)" + file: Figure_2_4_Trend_Analysis.png + occurrence: 1 + +fig_5: + type: FIG + title: "Trend Analysis Over Time (Yearly Data)" + file: Figure_2_5_Trend_Analysis.png + occurrence: 2 + +fig_6: + type: FIG + title: "Trend Analysis Over Time (Monthly Data)" + file: Figure_3_1_Trend_Analysis.png + +table_1: + type: TBL + title: "Summary of Sample Statistics" + file: Table_2_1_Sample_Statistics.csv + widths: NA + +table_2: + type: TBL + title: "Breakdown of Category X by Subcategory" + file: Table_2_2_Breakdown_of_Category_X.csv + widths: c(0.6,0.2,0.2) + +table_3: + type: TBL + title: "Yearly Performance Metrics" + file: Table_3_1_Yearly_Performance_Metrics.csv + widths: NA + + diff --git a/inst/use_cases/02_automated_reporting/example_yml_2.yml b/inst/use_cases/02_automated_reporting/example_yml_2.yml new file mode 100644 index 0000000..43aee1a --- /dev/null +++ b/inst/use_cases/02_automated_reporting/example_yml_2.yml @@ -0,0 +1,54 @@ +# yaml file with all outputs to be updated in example report + +fig_1: + type: FIG + title: "Data Point Distribution in Sample Set A" + file: Figure_2_1_Data_Point_Distribution.png + +fig_2: + type: FIG + title: "Comparative Analysis of Categories X and Y" + file: Figure_2_2_Comparative_Analysis.png + occurrence: 1 + +fig_3: + type: FIG + title: "Comparative Analysis of Categories X and Y" + file: lena.bmp + occurrence: 2 +fig_4: + type: FIG + title: "Trend Analysis Over Time (Yearly Data)" + file: Figure_2_4_Trend_Analysis.png + occurrence: 1 + +fig_5: + type: FIG + title: "Trend Analysis Over Time (Yearly Data)" + file: example_2_pages.pdf + occurrence: 2 + +fig_6: + type: FIG + title: "Trend Analysis Over Time (Monthly Data)" + file: Figure_3_1_Trend_Analysis.png + +table_1: + type: TBL + title: "Summary of Sample Statistics" + file: Table_2_1_Summary_of_Sample_Statistics.csv + widths: NA + +table_2: + type: TBL + title: "Breakdown of Category X by Subcategory" + file: Table_2_2_Breakdown_of_Category_X.csv + widths: c(0.6,0.2,0.2) + +table_3: + type: TBL + title: "Yearly Performance Metrics" + file: Table_3_1_Yearly_Performance_Metrics.csv + widths: NA + + diff --git a/man/ActivityTable.Rd b/man/ActivityTable.Rd new file mode 100644 index 0000000..8d1426e --- /dev/null +++ b/man/ActivityTable.Rd @@ -0,0 +1,165 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/ActivityTable.R +\name{ActivityTable} +\alias{ActivityTable} +\title{Activity Table class definition} +\description{ +This R6 class relates to the activity table in the generic report +that documents the person who perfomed the activity and the person who validated +and the respective dates. +Developer note: This class is developed using private and active fields rather +than public fields directly which facilitates more rigid behaviour after the +class has been initialised. If it is deemed that edit the fields after initialisation +is a rare case then we might consider simplifying these classes to use public +fields. +} +\examples{ + +## ------------------------------------------------ +## Method `ActivityTable$new` +## ------------------------------------------------ + +at <- ActivityTable$new( + activity = 'Super cool calculation', + main_author_name = 'David Bowie', + main_activity_date = '01-Oct-2024', + qc_author_name = 'Freddie Mercury', + qc_activity_date = '20-Oct-2024') + + +## ------------------------------------------------ +## Method `ActivityTable$get_table` +## ------------------------------------------------ + +at <- ActivityTable$new( + activity = 'Super cool calculation', + main_author_name = 'David Bowie', + main_activity_date = '01-Oct-2024', + qc_author_name = 'Freddie Mercury', + qc_activity_date = '20-Oct-2024') + +at$get_table() + +} +\section{Active bindings}{ +\if{html}{\out{
}} +\describe{ +\item{\code{activity}}{Active binding for setting the activity} + +\item{\code{main_author_name}}{Active binding for setting the main author name} + +\item{\code{main_activity_date}}{Active binding for setting the main acitivty date} + +\item{\code{qc_author_name}}{Active binding for setting the QC author name} + +\item{\code{qc_activity_date}}{Active binding for setting the QC activity date} +} +\if{html}{\out{
}} +} +\section{Methods}{ +\subsection{Public methods}{ +\itemize{ +\item \href{#method-ActivityTable-new}{\code{ActivityTable$new()}} +\item \href{#method-ActivityTable-get_table}{\code{ActivityTable$get_table()}} +\item \href{#method-ActivityTable-clone}{\code{ActivityTable$clone()}} +} +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-ActivityTable-new}{}}} +\subsection{Method \code{new()}}{ +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{ActivityTable$new( + activity = NA_character_, + main_author_name = NA_character_, + main_activity_date = NA_character_, + qc_author_name = NA_character_, + qc_activity_date = NA_character_ +)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{activity}}{Name of the activity that was performed} + +\item{\code{main_author_name}}{Name (Firstname Lastname) of the person who performed the activity} + +\item{\code{main_activity_date}}{Date (DD-Mmm-YYYY) when activity was performed.} + +\item{\code{qc_author_name}}{Name (Firstname Lastname) of reviewer.} + +\item{\code{qc_activity_date}}{Date (DD-Mmm-YYYY) when QC was performed.} +} +\if{html}{\out{
}} +} +\subsection{Examples}{ +\if{html}{\out{
}} +\preformatted{at <- ActivityTable$new( + activity = 'Super cool calculation', + main_author_name = 'David Bowie', + main_activity_date = '01-Oct-2024', + qc_author_name = 'Freddie Mercury', + qc_activity_date = '20-Oct-2024') + +} +\if{html}{\out{
}} + +} + +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-ActivityTable-get_table}{}}} +\subsection{Method \code{get_table()}}{ +Show activity table. Takes an object of class \code{\link[rdocx]{ActivityTable}}. +as input and generates Table 1 of the Generic Report Document. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{ActivityTable$get_table()}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{act}}{An object of class \code{\link[rdocx]{ActivityTable}}.} +} +\if{html}{\out{
}} +} +\subsection{Returns}{ +Table 1 of the Sample Size Document. +} +\subsection{Examples}{ +\if{html}{\out{
}} +\preformatted{at <- ActivityTable$new( + activity = 'Super cool calculation', + main_author_name = 'David Bowie', + main_activity_date = '01-Oct-2024', + qc_author_name = 'Freddie Mercury', + qc_activity_date = '20-Oct-2024') + +at$get_table() + +} +\if{html}{\out{
}} + +} + +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-ActivityTable-clone}{}}} +\subsection{Method \code{clone()}}{ +The objects of this class are cloneable with this method. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{ActivityTable$clone(deep = FALSE)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{deep}}{Whether to make a deep clone.} +} +\if{html}{\out{
}} +} +} +} diff --git a/man/ChangelogTable.Rd b/man/ChangelogTable.Rd new file mode 100644 index 0000000..3d65668 --- /dev/null +++ b/man/ChangelogTable.Rd @@ -0,0 +1,194 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/ChangelogTable.R +\docType{methods} +\name{ChangelogTable} +\alias{ChangelogTable} +\title{Changelog Table class definition} +\description{ +This R6 class controls one row of the Changelog table that documents +the date, version, reason and what changed in the update on the document. +It uses the ChangelogTableRow class to manage the different rows in the table, +and control the checks +} +\examples{ + +## ------------------------------------------------ +## Method `ChangelogTable$add_row` +## ------------------------------------------------ + +ctr <- ChangelogTableRow$new( + date = '01-Oct-2024', + version = 'Initial version', + why_update = 'Create first version of the document', + what_changed = 'Document creation') + +changelog_table <- ChangelogTable$new() +changelog_table$add_row(ctr) + + +## ------------------------------------------------ +## Method `ChangelogTable$to_dataframe` +## ------------------------------------------------ + +ctr <- ChangelogTableRow$new( + date = '01-Oct-2024', + version = 'Initial version', + why_update = 'Create first versoin of the document', + what_changed = 'Document creation') + +changelog_table <- ChangelogTable$new() +changelog_table$add_row(ctr) +changelog_table$to_dataframe() + + +## ------------------------------------------------ +## Method `ChangelogTable$get_table` +## ------------------------------------------------ + +ctr_1 <- ChangelogTableRow$new( + date = '01-Oct-2024', + version = 'Initial version', + why_update = 'Create first versoin of the document', + what_changed = 'Document creation') + + ctr_2 <- ChangelogTableRow$new( + date = '15-Oct-2024', + version = 'Final version', + why_update = 'Change calculation of param 1', + what_changed = 'Section 2') + +changelog_table <- ChangelogTable$new() +changelog_table$add_row(ctr_1) +changelog_table$add_row(ctr_2) +changelog_table$get_table() + +} +\section{Public fields}{ +\if{html}{\out{
}} +\describe{ +\item{\code{rows}}{List where new rows are added} +} +\if{html}{\out{
}} +} +\section{Methods}{ +\subsection{Public methods}{ +\itemize{ +\item \href{#method-ChangelogTable-add_row}{\code{ChangelogTable$add_row()}} +\item \href{#method-ChangelogTable-to_dataframe}{\code{ChangelogTable$to_dataframe()}} +\item \href{#method-ChangelogTable-get_table}{\code{ChangelogTable$get_table()}} +\item \href{#method-ChangelogTable-clone}{\code{ChangelogTable$clone()}} +} +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-ChangelogTable-add_row}{}}} +\subsection{Method \code{add_row()}}{ +Takes an object of class ChangelogTableRow. +as input and adds it to the list of rows. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{ChangelogTable$add_row(changelog_table_row_object)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{changelog_table_row_object}}{object of class ChangelogTableRow} +} +\if{html}{\out{
}} +} +\subsection{Examples}{ +\if{html}{\out{
}} +\preformatted{ctr <- ChangelogTableRow$new( + date = '01-Oct-2024', + version = 'Initial version', + why_update = 'Create first version of the document', + what_changed = 'Document creation') + +changelog_table <- ChangelogTable$new() +changelog_table$add_row(ctr) + +} +\if{html}{\out{
}} + +} + +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-ChangelogTable-to_dataframe}{}}} +\subsection{Method \code{to_dataframe()}}{ +Create a dataframe from all the rows in the row +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{ChangelogTable$to_dataframe()}\if{html}{\out{
}} +} + +\subsection{Examples}{ +\if{html}{\out{
}} +\preformatted{ctr <- ChangelogTableRow$new( + date = '01-Oct-2024', + version = 'Initial version', + why_update = 'Create first versoin of the document', + what_changed = 'Document creation') + +changelog_table <- ChangelogTable$new() +changelog_table$add_row(ctr) +changelog_table$to_dataframe() + +} +\if{html}{\out{
}} + +} + +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-ChangelogTable-get_table}{}}} +\subsection{Method \code{get_table()}}{ +Generates the changelog table usin to_dataframe() +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{ChangelogTable$get_table()}\if{html}{\out{
}} +} + +\subsection{Examples}{ +\if{html}{\out{
}} +\preformatted{ctr_1 <- ChangelogTableRow$new( + date = '01-Oct-2024', + version = 'Initial version', + why_update = 'Create first versoin of the document', + what_changed = 'Document creation') + + ctr_2 <- ChangelogTableRow$new( + date = '15-Oct-2024', + version = 'Final version', + why_update = 'Change calculation of param 1', + what_changed = 'Section 2') + +changelog_table <- ChangelogTable$new() +changelog_table$add_row(ctr_1) +changelog_table$add_row(ctr_2) +changelog_table$get_table() + +} +\if{html}{\out{
}} + +} + +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-ChangelogTable-clone}{}}} +\subsection{Method \code{clone()}}{ +The objects of this class are cloneable with this method. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{ChangelogTable$clone(deep = FALSE)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{deep}}{Whether to make a deep clone.} +} +\if{html}{\out{
}} +} +} +} diff --git a/man/ChangelogTableRow.Rd b/man/ChangelogTableRow.Rd new file mode 100644 index 0000000..4481b77 --- /dev/null +++ b/man/ChangelogTableRow.Rd @@ -0,0 +1,100 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/ChangelogTable.R +\name{ChangelogTableRow} +\alias{ChangelogTableRow} +\title{Changelog Table Row class definition} +\description{ +This R6 class controls one row of the Changelog table that documents +the date, version, reason and what changed in the update on the document. +} +\examples{ + +## ------------------------------------------------ +## Method `ChangelogTableRow$new` +## ------------------------------------------------ + +ctr <- ChangelogTableRow$new( + date = '01-Oct-2024', + version = 'Initial version', + why_update = 'Create first version of the document', + what_changed = 'Document creation') + +} +\section{Active bindings}{ +\if{html}{\out{
}} +\describe{ +\item{\code{date}}{Active binding for setting the date of changes} + +\item{\code{version}}{Active binding for setting the version of the document} + +\item{\code{why_update}}{Active binding for setting the reason for the update} + +\item{\code{what_changed}}{Active binding for setting the section and title impacted} +} +\if{html}{\out{
}} +} +\section{Methods}{ +\subsection{Public methods}{ +\itemize{ +\item \href{#method-ChangelogTableRow-new}{\code{ChangelogTableRow$new()}} +\item \href{#method-ChangelogTableRow-clone}{\code{ChangelogTableRow$clone()}} +} +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-ChangelogTableRow-new}{}}} +\subsection{Method \code{new()}}{ +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{ChangelogTableRow$new( + date = NA_character_, + version = NA_character_, + why_update = NA_character_, + what_changed = NA_character_ +)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{date}}{String. Date (DD-Mmm-YYYY) when modifications were performed} + +\item{\code{version}}{String. Document version} + +\item{\code{why_update}}{String. Reason for updating the document} + +\item{\code{what_changed}}{String. Sections and titles that where impacted with the modifications} +} +\if{html}{\out{
}} +} +\subsection{Examples}{ +\if{html}{\out{
}} +\preformatted{ctr <- ChangelogTableRow$new( + date = '01-Oct-2024', + version = 'Initial version', + why_update = 'Create first version of the document', + what_changed = 'Document creation') + +} +\if{html}{\out{
}} + +} + +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-ChangelogTableRow-clone}{}}} +\subsection{Method \code{clone()}}{ +The objects of this class are cloneable with this method. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{ChangelogTableRow$clone(deep = FALSE)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{deep}}{Whether to make a deep clone.} +} +\if{html}{\out{
}} +} +} +} diff --git a/man/ReplaceOutputs.Rd b/man/ReplaceOutputs.Rd new file mode 100644 index 0000000..3a551fd --- /dev/null +++ b/man/ReplaceOutputs.Rd @@ -0,0 +1,267 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/ReplaceOutputs.R +\name{ReplaceOutputs} +\alias{ReplaceOutputs} +\title{Replace Outputs in a word document} +\description{ +This R6 class allows to, given a template word document and a list +of outputs, to add them at the given location in the text. It automates the +time-consuming task of adding and formatting tables and figures to a word document. +The two main inputs of the class are a template docx file, in this case a clinical +trial report and a yaml file that will contain the names of the table/figures to be replaced +and pointing to the new ones. +The yml file, should have predefined structure and parameters to be set: +\itemize{ +\item type. Indicates if it is a table (TBL) or a figure (FIG) +\item title. The title or caption of the table/figure in the template docx file +\item file. The name of the file (csv or image) to be used in the new version of the docx +\item widths. For tables only, a pre-defined column widths (useful for model parameter table, for example) +\item occurrence. Int. To be used when captions are duplicated for more than one +figure/table. To set the order they appear in the word document. +} + +An example of the yaml structure: + +\preformatted{ +output_1: + type: TBL + title: "Caption of the output 1" + file: output_1.csv + widths: ~ + occurrence: ~ +output_2: + type: FIG + title: "Caption of the output 2" + file: output_2.png + } +} +\examples{ + +## ------------------------------------------------ +## Method `ReplaceOutputs$new` +## ------------------------------------------------ + +uo <- ReplaceOutputs$new( + template_docx_filename = system.file("use_cases/02_automated_reporting", + "Automated_Reporting_Example.docx", + package="rdocx"), + outputs_path = system.file("use_cases/02_automated_reporting/example_outputs", + package="rdocx"), + doc_final_filename = "./test.docx", + yml_filename = system.file("use_cases/02_automated_reporting", + "example_yml_1.yml", + package="rdocx") + ) + + +## ------------------------------------------------ +## Method `ReplaceOutputs$get_captions_to_yml` +## ------------------------------------------------ + +uo <- ReplaceOutputs$new( + template_docx_filename = system.file("use_cases/02_automated_reporting", + "Automated_Reporting_Example.docx", + package="rdocx"), + outputs_path = system.file("use_cases/02_automated_reporting/example_outputs", + package="rdocx"), + doc_final_filename = "./test.docx" + ) +uo$get_captions_to_yml(yml_caption_filename="./test_yml.yml") + + +## ------------------------------------------------ +## Method `ReplaceOutputs$update_all_outputs` +## ------------------------------------------------ + +uo <- ReplaceOutputs$new( + template_docx_filename = system.file("use_cases/02_automated_reporting", + "Automated_Reporting_Example.docx", + package="rdocx"), + outputs_path = system.file("use_cases/02_automated_reporting/example_outputs", + package="rdocx"), + doc_final_filename = "./test.docx", + yml_filename = system.file("use_cases/02_automated_reporting", + "example_yml_1.yml", + package="rdocx") + ) +uo$update_all_outputs() +} +\section{Active bindings}{ +\if{html}{\out{
}} +\describe{ +\item{\code{template_docx_filename}}{Active binding for setting the path to the template docx} + +\item{\code{outputs_path}}{Active binding for setting the path to the folder with the outputs} + +\item{\code{doc_final_filename}}{Active binding for setting the path for the updated docx file} + +\item{\code{yml_filename}}{Active binding for setting the path to the yaml file} +} +\if{html}{\out{
}} +} +\section{Methods}{ +\subsection{Public methods}{ +\itemize{ +\item \href{#method-ReplaceOutputs-new}{\code{ReplaceOutputs$new()}} +\item \href{#method-ReplaceOutputs-get_captions_to_yml}{\code{ReplaceOutputs$get_captions_to_yml()}} +\item \href{#method-ReplaceOutputs-update_all_outputs}{\code{ReplaceOutputs$update_all_outputs()}} +\item \href{#method-ReplaceOutputs-clone}{\code{ReplaceOutputs$clone()}} +} +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-ReplaceOutputs-new}{}}} +\subsection{Method \code{new()}}{ +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{ReplaceOutputs$new( + template_docx_filename = NA_character_, + outputs_path = NA_character_, + doc_final_filename = NA_character_, + yml_filename = NA_character_ +)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{template_docx_filename}}{String. File name of the template docx to be +modified. For example, "/path/to/file/filename.docx"} + +\item{\code{outputs_path}}{String. Path to the folder with the outputs (figures +and tables-csvs) that will be added to the new version of the docx} + +\item{\code{doc_final_filename}}{String. File name of the updated version of the +docx file. For example, "/path/to/file/filename_updated.docx"} + +\item{\code{yml_filename}}{String. File name of the yaml file that will guide the +replacement of the outputs (tables and figures) in the updated docx. For +example, "/path/to/file/filename.yml"} +} +\if{html}{\out{
}} +} +\subsection{Examples}{ +\if{html}{\out{
}} +\preformatted{uo <- ReplaceOutputs$new( + template_docx_filename = system.file("use_cases/02_automated_reporting", + "Automated_Reporting_Example.docx", + package="rdocx"), + outputs_path = system.file("use_cases/02_automated_reporting/example_outputs", + package="rdocx"), + doc_final_filename = "./test.docx", + yml_filename = system.file("use_cases/02_automated_reporting", + "example_yml_1.yml", + package="rdocx") + ) + +} +\if{html}{\out{
}} + +} + +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-ReplaceOutputs-get_captions_to_yml}{}}} +\subsection{Method \code{get_captions_to_yml()}}{ +Get captions to yaml +Given a docx file, it scans the document to find the Figures and Tables captions +and provides a yaml file with all the figures and tables found. It also scans +the document for possible "Source:" which has to be after a placeholder figure/table. +If source was found, it will be added to "file" param in the yaml file. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{ReplaceOutputs$get_captions_to_yml(yml_caption_filename)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{yml_caption_filename}}{String. File name of the newly created yaml +file that will contain the Figures and Tables of in the provided docx document. +For example, "/path/to/file/filename.yml" +It will follow the required format for the replacement of the outputs (tables +and figures) to update docx. +\preformatted{ +output_1: + type: TBL + title: "Caption of the output 1" + file: ~ + widths: ~ + occurrence: ~ +output_2: + type: FIG + title: "Caption of the output 2" + file: ~ + widths: ~ + occurrence: ~ + }} +} +\if{html}{\out{
}} +} +\subsection{Examples}{ +\if{html}{\out{
}} +\preformatted{uo <- ReplaceOutputs$new( + template_docx_filename = system.file("use_cases/02_automated_reporting", + "Automated_Reporting_Example.docx", + package="rdocx"), + outputs_path = system.file("use_cases/02_automated_reporting/example_outputs", + package="rdocx"), + doc_final_filename = "./test.docx" + ) +uo$get_captions_to_yml(yml_caption_filename="./test_yml.yml") + +} +\if{html}{\out{
}} + +} + +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-ReplaceOutputs-update_all_outputs}{}}} +\subsection{Method \code{update_all_outputs()}}{ +Update all outputs. +It iterates through all the outputs in the yml file, and assigns them in the +correct location in the updated docx. +If no "file" param given (NA or NULL), it will skip the output. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{ReplaceOutputs$update_all_outputs()}\if{html}{\out{
}} +} + +\subsection{Examples}{ +\if{html}{\out{
}} +\preformatted{uo <- ReplaceOutputs$new( + template_docx_filename = system.file("use_cases/02_automated_reporting", + "Automated_Reporting_Example.docx", + package="rdocx"), + outputs_path = system.file("use_cases/02_automated_reporting/example_outputs", + package="rdocx"), + doc_final_filename = "./test.docx", + yml_filename = system.file("use_cases/02_automated_reporting", + "example_yml_1.yml", + package="rdocx") + ) +uo$update_all_outputs() +} +\if{html}{\out{
}} + +} + +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-ReplaceOutputs-clone}{}}} +\subsection{Method \code{clone()}}{ +The objects of this class are cloneable with this method. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{ReplaceOutputs$clone(deep = FALSE)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{deep}}{Whether to make a deep clone.} +} +\if{html}{\out{
}} +} +} +} diff --git a/man/SignaturesTable.Rd b/man/SignaturesTable.Rd new file mode 100644 index 0000000..2a311a3 --- /dev/null +++ b/man/SignaturesTable.Rd @@ -0,0 +1,190 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/SignaturesTable.R +\docType{methods} +\name{SignaturesTable} +\alias{SignaturesTable} +\title{Signatures Table class definition} +\description{ +This R6 class controls one row of the Signatures table that documents +the date, version, reason and what changed in the update on the document. +It uses the ChangelogTableRow class to manage the different rows in the table, +and control the checks +} +\examples{ + +## ------------------------------------------------ +## Method `SignaturesTable$add_row` +## ------------------------------------------------ + +str <- SignaturesTableRow$new( + name = 'Harry Styles', + department = 'eCompliance', + date = '15-Feb-2024') + + +signatures_table <- SignaturesTable$new() +signatures_table$add_row(str) + + +## ------------------------------------------------ +## Method `SignaturesTable$to_dataframe` +## ------------------------------------------------ + +str <- SignaturesTableRow$new( + name = 'Harry Styles', + department = 'eCompliance', + date = '15-Feb-2024') + + +signatures_table <- SignaturesTable$new() +signatures_table$add_row(str) +signatures_table$to_dataframe() + + +## ------------------------------------------------ +## Method `SignaturesTable$get_table` +## ------------------------------------------------ + +str_1 <- SignaturesTableRow$new( + name = 'Harry Styles', + department = 'eCompliance', + date = '15-Feb-2024') + +str_2 <- SignaturesTableRow$new( + name = 'Alex Turner', + department = 'Product Development', + date = '20-Feb-2024') + +signatures_table <- SignaturesTable$new() +signatures_table$add_row(str_1) +signatures_table$add_row(str_2) +signatures_table$get_table() + +} +\section{Public fields}{ +\if{html}{\out{
}} +\describe{ +\item{\code{rows}}{List where new rows are added} +} +\if{html}{\out{
}} +} +\section{Methods}{ +\subsection{Public methods}{ +\itemize{ +\item \href{#method-SignaturesTable-add_row}{\code{SignaturesTable$add_row()}} +\item \href{#method-SignaturesTable-to_dataframe}{\code{SignaturesTable$to_dataframe()}} +\item \href{#method-SignaturesTable-get_table}{\code{SignaturesTable$get_table()}} +\item \href{#method-SignaturesTable-clone}{\code{SignaturesTable$clone()}} +} +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-SignaturesTable-add_row}{}}} +\subsection{Method \code{add_row()}}{ +Takes an object of class SignaturesTableRow. +as input and adds it to the list of rows. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{SignaturesTable$add_row(signatures_table_row_object)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{signatures_table_row_object}}{object of class SignaturesTableRow} +} +\if{html}{\out{
}} +} +\subsection{Examples}{ +\if{html}{\out{
}} +\preformatted{str <- SignaturesTableRow$new( + name = 'Harry Styles', + department = 'eCompliance', + date = '15-Feb-2024') + + +signatures_table <- SignaturesTable$new() +signatures_table$add_row(str) + +} +\if{html}{\out{
}} + +} + +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-SignaturesTable-to_dataframe}{}}} +\subsection{Method \code{to_dataframe()}}{ +Create a dataframe from all the rows in the row +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{SignaturesTable$to_dataframe()}\if{html}{\out{
}} +} + +\subsection{Examples}{ +\if{html}{\out{
}} +\preformatted{str <- SignaturesTableRow$new( + name = 'Harry Styles', + department = 'eCompliance', + date = '15-Feb-2024') + + +signatures_table <- SignaturesTable$new() +signatures_table$add_row(str) +signatures_table$to_dataframe() + +} +\if{html}{\out{
}} + +} + +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-SignaturesTable-get_table}{}}} +\subsection{Method \code{get_table()}}{ +Generates the signature table usin to_dataframe() +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{SignaturesTable$get_table()}\if{html}{\out{
}} +} + +\subsection{Examples}{ +\if{html}{\out{
}} +\preformatted{str_1 <- SignaturesTableRow$new( + name = 'Harry Styles', + department = 'eCompliance', + date = '15-Feb-2024') + +str_2 <- SignaturesTableRow$new( + name = 'Alex Turner', + department = 'Product Development', + date = '20-Feb-2024') + +signatures_table <- SignaturesTable$new() +signatures_table$add_row(str_1) +signatures_table$add_row(str_2) +signatures_table$get_table() + +} +\if{html}{\out{
}} + +} + +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-SignaturesTable-clone}{}}} +\subsection{Method \code{clone()}}{ +The objects of this class are cloneable with this method. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{SignaturesTable$clone(deep = FALSE)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{deep}}{Whether to make a deep clone.} +} +\if{html}{\out{
}} +} +} +} diff --git a/man/SignaturesTableRow.Rd b/man/SignaturesTableRow.Rd new file mode 100644 index 0000000..c05c73a --- /dev/null +++ b/man/SignaturesTableRow.Rd @@ -0,0 +1,93 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/SignaturesTable.R +\name{SignaturesTableRow} +\alias{SignaturesTableRow} +\title{Signatures Table Row class definition} +\description{ +This R6 class controls one row of the Signatures table that documents +the date, version, reason and what changed in the update on the document. +} +\examples{ + +## ------------------------------------------------ +## Method `SignaturesTableRow$new` +## ------------------------------------------------ + +str <- SignaturesTableRow$new( + name = 'Harry Styles', + department = 'eCompliance', + date = '15-Feb-2024') + +} +\section{Active bindings}{ +\if{html}{\out{
}} +\describe{ +\item{\code{name}}{Active binding for setting the name of the person who will sign} + +\item{\code{department}}{Active binding for setting the department of the person who signs} + +\item{\code{date}}{Active binding for setting the date of sign} +} +\if{html}{\out{
}} +} +\section{Methods}{ +\subsection{Public methods}{ +\itemize{ +\item \href{#method-SignaturesTableRow-new}{\code{SignaturesTableRow$new()}} +\item \href{#method-SignaturesTableRow-clone}{\code{SignaturesTableRow$clone()}} +} +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-SignaturesTableRow-new}{}}} +\subsection{Method \code{new()}}{ +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{SignaturesTableRow$new( + name = NA_character_, + department = NA_character_, + date = NA_character_ +)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{name}}{String. Name of the person who will sign the document} + +\item{\code{department}}{String. Department of the person who will sign the document} + +\item{\code{date}}{String. Date (DD-Mmm-YYYY) when the document will be signed} +} +\if{html}{\out{
}} +} +\subsection{Examples}{ +\if{html}{\out{
}} +\preformatted{str <- SignaturesTableRow$new( + name = 'Harry Styles', + department = 'eCompliance', + date = '15-Feb-2024') + +} +\if{html}{\out{
}} + +} + +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-SignaturesTableRow-clone}{}}} +\subsection{Method \code{clone()}}{ +The objects of this class are cloneable with this method. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{SignaturesTableRow$clone(deep = FALSE)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{deep}}{Whether to make a deep clone.} +} +\if{html}{\out{
}} +} +} +} diff --git a/man/TitlePage.Rd b/man/TitlePage.Rd new file mode 100644 index 0000000..47e7db4 --- /dev/null +++ b/man/TitlePage.Rd @@ -0,0 +1,213 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/TitlePage.R +\name{TitlePage} +\alias{TitlePage} +\alias{get_title_page} +\title{Title page class definition} +\description{ +This R6 class relates to the Title Page in a report that adds all +the required fields of the Generic Report Title Page +Developer note: This class is developed using private and active fields rather +than public fields directly which facilitates more rigid behaviour after the +class has been initialised. If it is deemed that edit the fields after initialisation +is a rare case then we might consider simplifying these classes to use public +fields. +} +\examples{ + +## ------------------------------------------------ +## Method `TitlePage$new` +## ------------------------------------------------ + +\dontrun{ +tp = TitlePage$new( + report_title = "Super cool document", + department = "Cool department", + study_title = "Compute super cool things", + study_number = "OWNDJQW9923", + status = "Draft", + version = "1", + date = "01-Feb-2024", + bus_class = "Very confidential") + } + +## ------------------------------------------------ +## Method `TitlePage$get_title_page` +## ------------------------------------------------ + +\dontrun{ +tp = TitlePage$new( + report_title= "Super cool document", + department = "Cool department", + study_title = "Compute super cool things", + study_number = "OWNDJQW9923", + status = "Draft", + version = "1", + date = "01-Feb-2024", + bus_class = "Very confidential") +tp$get_title_page() +} +} +\section{Active bindings}{ +\if{html}{\out{
}} +\describe{ +\item{\code{report_title}}{Active binding for setting the report title} + +\item{\code{department}}{Active binding for setting the department} + +\item{\code{study_title}}{Active binding for setting the study title} + +\item{\code{study_number}}{Active binding for setting the study number} + +\item{\code{status}}{Active binding for setting the document status} + +\item{\code{version}}{Active binding for setting the document version} + +\item{\code{date}}{Active binding for setting the date} + +\item{\code{bus_class}}{Active binding for setting the business classification} + +\item{\code{changelog_table}}{Active binding for setting the changelog table} + +\item{\code{signatures_table}}{Active binding for setting the signatures table} +} +\if{html}{\out{
}} +} +\section{Methods}{ +\subsection{Public methods}{ +\itemize{ +\item \href{#method-TitlePage-new}{\code{TitlePage$new()}} +\item \href{#method-TitlePage-get_title_page}{\code{TitlePage$get_title_page()}} +\item \href{#method-TitlePage-clone}{\code{TitlePage$clone()}} +} +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-TitlePage-new}{}}} +\subsection{Method \code{new()}}{ +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{TitlePage$new( + report_title = NA_character_, + department = NA_character_, + study_title = NA_character_, + study_number = NA_character_, + status = c("Draft", "Final"), + version = NA_character_, + date = NA_character_, + bus_class = NA_character_, + changelog_table = NULL, + signatures_table = NULL +)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{report_title}}{String. Report title.} + +\item{\code{department}}{String. Department.} + +\item{\code{study_title}}{String. Study title of the document} + +\item{\code{study_number}}{String. Study number of the document} + +\item{\code{status}}{String. Status of the document, with possible values \code{Draft} or \code{Final}} + +\item{\code{version}}{String. Version of the document.} + +\item{\code{date}}{String. Date (DD-Mmm-YYYY) of the final document release.} + +\item{\code{bus_class}}{String. Business Classification.} + +\item{\code{changelog_table}}{Flextable. Table to be added after the title page (Optional)} + +\item{\code{signatures_table}}{Flextable. Table to be added after the title page (Optional)} +} +\if{html}{\out{
}} +} +\subsection{Examples}{ +\if{html}{\out{
}} +\preformatted{\dontrun{ +tp = TitlePage$new( + report_title = "Super cool document", + department = "Cool department", + study_title = "Compute super cool things", + study_number = "OWNDJQW9923", + status = "Draft", + version = "1", + date = "01-Feb-2024", + bus_class = "Very confidential") + } +} +\if{html}{\out{
}} + +} + +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-TitlePage-get_title_page}{}}} +\subsection{Method \code{get_title_page()}}{ +Generate title page. Takes an object of class \code{\link[rdocx]{TitlePage}}. +as input and generates the title page. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{TitlePage$get_title_page( + reference_docx = generic_report_template(), + output_path = "." +)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{reference_docx}}{String. Template document in .docx format providing +styling and structure information for rendering your docx report. +This is configured to be Generic Report Template using the function +\code{generic_report_template()}.} + +\item{\code{output_path}}{String. Path where the title page in docx format should +be written. Default is the current working directory.} +} +\if{html}{\out{
}} +} +\subsection{Returns}{ +A title page in Word format ('_generic_report_title.docx'). +} +\subsection{Examples}{ +\if{html}{\out{
}} +\preformatted{\dontrun{ +tp = TitlePage$new( + report_title= "Super cool document", + department = "Cool department", + study_title = "Compute super cool things", + study_number = "OWNDJQW9923", + status = "Draft", + version = "1", + date = "01-Feb-2024", + bus_class = "Very confidential") +tp$get_title_page() +} +} +\if{html}{\out{
}} + +} + +} +\if{html}{\out{
}} +\if{html}{\out{}} +\if{latex}{\out{\hypertarget{method-TitlePage-clone}{}}} +\subsection{Method \code{clone()}}{ +The objects of this class are cloneable with this method. +\subsection{Usage}{ +\if{html}{\out{
}}\preformatted{TitlePage$clone(deep = FALSE)}\if{html}{\out{
}} +} + +\subsection{Arguments}{ +\if{html}{\out{
}} +\describe{ +\item{\code{deep}}{Whether to make a deep clone.} +} +\if{html}{\out{
}} +} +} +} diff --git a/man/change_figure.Rd b/man/change_figure.Rd new file mode 100644 index 0000000..1fccfd3 --- /dev/null +++ b/man/change_figure.Rd @@ -0,0 +1,26 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{change_figure} +\alias{change_figure} +\title{Change figure in a docx} +\usage{ +change_figure(template_doc, full_path, figure) +} +\arguments{ +\item{template_doc}{Doc to modify} + +\item{full_path}{Path to the folder where the figrue is} + +\item{figure}{Figure to be added} +} +\value{ +Updated document with new figure +} +\description{ +Change figure in a docx +} +\examples{ +\dontrun{ +change_figure(doc, "/path/to/output", "output.png") +} +} diff --git a/man/change_table.Rd b/man/change_table.Rd new file mode 100644 index 0000000..3ba9bd8 --- /dev/null +++ b/man/change_table.Rd @@ -0,0 +1,28 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{change_table} +\alias{change_table} +\title{Change table in a docx} +\usage{ +change_table(template_doc, full_path, table, widths = NA) +} +\arguments{ +\item{template_doc}{Doc to modify} + +\item{full_path}{Path tot eh folder where the csv is} + +\item{table}{Table to be added} + +\item{widths}{If there are any pre-specified widths for columns in table} +} +\value{ +Updated document with new table +} +\description{ +Change table in a docx +} +\examples{ +\dontrun{ +change_table(doc, "data.csv", widths = NA) +} +} diff --git a/man/check_name.Rd b/man/check_name.Rd new file mode 100644 index 0000000..2b8fc9f --- /dev/null +++ b/man/check_name.Rd @@ -0,0 +1,23 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils_checks.R +\name{check_name} +\alias{check_name} +\title{Check name provided} +\usage{ +check_name(name_str) +} +\arguments{ +\item{name_str}{Name to be checked} +} +\value{ +the original date str if matches conditions, FALSE if not +} +\description{ +Check to see if name has Firstname Lastname and provide a custom +error to guide users +} +\examples{ +\dontrun{ +check_name("Harry Styles") +} +} diff --git a/man/check_null_or_flextable.Rd b/man/check_null_or_flextable.Rd new file mode 100644 index 0000000..581aa52 --- /dev/null +++ b/man/check_null_or_flextable.Rd @@ -0,0 +1,23 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils_checks.R +\name{check_null_or_flextable} +\alias{check_null_or_flextable} +\title{Check for NULL or flextable} +\usage{ +check_null_or_flextable(variable) +} +\arguments{ +\item{variable}{object to check} +} +\value{ +the variable if matches conditions, error if not +} +\description{ +Checks if the objects passed is either NULL or a flextable +} +\examples{ +\dontrun{ +ft <- flextable::flextable(head(mtcars)) +check_null_or_flextable(ft) +} +} diff --git a/man/check_string_is_date.Rd b/man/check_string_is_date.Rd new file mode 100644 index 0000000..8dfd76e --- /dev/null +++ b/man/check_string_is_date.Rd @@ -0,0 +1,24 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils_checks.R +\name{check_string_is_date} +\alias{check_string_is_date} +\title{Check date provided} +\usage{ +check_string_is_date(date_str, date_format = "\%d-\%b-\%Y") +} +\arguments{ +\item{date_str}{String. Candidate string to check.} + +\item{date_format}{String. Expected date format. Default is dd-Mmm-yyyy.} +} +\value{ +the original date str if matches conditions, FALSE if not +} +\description{ +Check to see if a string is a date format +} +\examples{ +\dontrun{ +check_string_is_date("01-Oct-2023") +} +} diff --git a/man/create_yml.Rd b/man/create_yml.Rd new file mode 100644 index 0000000..b72cdbc --- /dev/null +++ b/man/create_yml.Rd @@ -0,0 +1,48 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{create_yml} +\alias{create_yml} +\title{Create caption yaml} +\usage{ +create_yml(figure_table_captions_sources, yaml_filename) +} +\arguments{ +\item{figure_table_captions_sources}{List of the tables and figures with its captions} + +\item{yaml_filename}{String. Output yaml filename, e.g. "/path/to/file/filename.yml"} +} +\description{ +Given a list of captions, it creates a template yaml file with +\itemize{ +\item type. Indicates if it is a table (TBL) or a figure (FIG) +\item title. The title or caption of the table/figure in the template docx file +\item file. The name of the file (csv or image) to be used in the new version of the docx +\item widths. For tables only, a pre-defined column widths (useful for model parameter table, for example) +An example of the yaml structure: +\item occurrence. Int. To be used when captions are duplicated for more than one +figure/table. To set the order they appear in the word document. +} + +\preformatted{ +output_1: + type: TBL + title: "Caption of the output 1" + file: ~ + widths: ~ + occurrence: ~ +output_2: + type: FIG + title: "Caption of the output 2" + file: ~ + widths: ~ + occurrence: ~ + } +} +\examples{ +\dontrun{ +captions <- extract_figure_table_captions(system.file("use_cases/02_automated_reporting", + "Automated_Reporting_Example.docx", + package="rdocx")) +create_yml(captions, "./example_yml.docx") +} +} diff --git a/man/cursor_reach_list.Rd b/man/cursor_reach_list.Rd new file mode 100644 index 0000000..bb9d734 --- /dev/null +++ b/man/cursor_reach_list.Rd @@ -0,0 +1,28 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{cursor_reach_list} +\alias{cursor_reach_list} +\title{Cursor reach list} +\usage{ +cursor_reach_list(x, keyword) +} +\arguments{ +\item{x}{Document to be searched, already read by officer} + +\item{keyword}{String. String to be found in the docx document} +} +\value{ +List containing the positions where the keyword was found and the +corresponding text associated, as well as the positions where the possible source +path could be and its associated source path text +} +\description{ +This function read a word document with xml2, looks for a pattern +in the text (i.e Figure/tables captions)/ Return the positions and text for this +search, and also possible sources path underneath the placeholder figure/tables +} +\examples{ +\dontrun{ +cursor_reach_list(doc, "example caption") +} +} diff --git a/man/escape_caption.Rd b/man/escape_caption.Rd new file mode 100644 index 0000000..011b04d --- /dev/null +++ b/man/escape_caption.Rd @@ -0,0 +1,33 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{escape_caption} +\alias{escape_caption} +\title{Escape special characters in a string for regular expresion} +\usage{ +escape_caption(caption) +} +\arguments{ +\item{caption}{Caption/Title of a figure} +} +\value{ +escaped caption +} +\description{ +This function escapes special characters in a given caption +string using the gsub function. Special characters can cause issues in certain +contexts, such as regular expressions or when processing text data. By escaping +these characters, the resulting string can be safely used in various applications. +} +\details{ +The function uses the gsub function to replace special characters in the +input caption with their escaped versions. The regular expression pattern includes +a character class specifying the set of characters to be escaped. +The replacement string contains four backslashes followed by the matched special +character to ensure proper escaping. +} +\examples{ +\dontrun{ +caption <- "This is a figure title (an a parenthesis use!)" +escape_caption(caption) +} +} diff --git a/man/extract_figure_table_captions.Rd b/man/extract_figure_table_captions.Rd new file mode 100644 index 0000000..dbff597 --- /dev/null +++ b/man/extract_figure_table_captions.Rd @@ -0,0 +1,34 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{extract_figure_table_captions} +\alias{extract_figure_table_captions} +\title{Extract figure and table captions} +\usage{ +extract_figure_table_captions(doc_path) +} +\arguments{ +\item{doc_path}{String. Path to the docx document} +} +\value{ +List figure_table_captions_sources contaning the figure_table_captions +and its corresponding sources paths +} +\description{ +given a word document, extract all the caption for figures and +tables using 'cursor_reach_list()'. If the docx has a ToC, 'cursor_reach_list()' +will return captions from both the body of the text and the ToC. For that, +'extract_figure_table_captions()' cleans the captions based on keywords +contained internally in the docx. ToC captions (keyword PAGEREF) but the first +caption one is always missed by the searching function. Body captions +(keyword STYLEREF or nothing) are always found correctly. +Therefore, if STYLEREF is present we filter for those. If there is no STYLEREF +but there is PAGEREF, we take the ones that are not PAGEREF. If not STYLEREF +or PAGEREF, no need to clean anything. +} +\examples{ +\dontrun{ +extract_figure_table_captions(system.file("use_cases/02_automated_reporting", + "Automated_Reporting_Example.docx", + package="rdocx")) +} +} diff --git a/man/figures/hex-rdocx.png b/man/figures/hex-rdocx.png new file mode 100644 index 0000000..03fbcc5 Binary files /dev/null and b/man/figures/hex-rdocx.png differ diff --git a/man/find_and_delete_output.Rd b/man/find_and_delete_output.Rd new file mode 100644 index 0000000..521c093 --- /dev/null +++ b/man/find_and_delete_output.Rd @@ -0,0 +1,34 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{find_and_delete_output} +\alias{find_and_delete_output} +\title{Find and delete output in Word doc} +\usage{ +find_and_delete_output(template_docx, output_title, output_type, occurrence) +} +\arguments{ +\item{template_docx}{Docx document to be modified} + +\item{output_title}{Title of the output to search for} + +\item{output_type}{Type (Table or Figure)} + +\item{occurrence}{Int. Optional, when title is repeated is used to order them} +} +\value{ +doc_updated +} +\description{ +After escaping the output title or caption, it creates a regular +expression to find it in the text. If no occurrence is provided, it searches +using 'cursor_reach()'. If occurrence sis provided uses the utils function +'cursor_reach_list()' +} +\examples{ +\dontrun{ +find_and_delete_output(doc, + "Number of subjects", + "tbl", + 1) +} +} diff --git a/man/generic_report_template.Rd b/man/generic_report_template.Rd new file mode 100644 index 0000000..b456e1e --- /dev/null +++ b/man/generic_report_template.Rd @@ -0,0 +1,11 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/rdocx_global.R +\name{generic_report_template} +\alias{generic_report_template} +\title{Generic Report Template location} +\usage{ +generic_report_template() +} +\description{ +Function which returns the location of the generic report template +} diff --git a/man/remove_fig_table_part.Rd b/man/remove_fig_table_part.Rd new file mode 100644 index 0000000..019b6e7 --- /dev/null +++ b/man/remove_fig_table_part.Rd @@ -0,0 +1,28 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{remove_fig_table_part} +\alias{remove_fig_table_part} +\title{Remove Figure/Table part of caption} +\usage{ +remove_fig_table_part(caption) +} +\arguments{ +\item{caption}{String. Caption that need to be modified} +} +\value{ +updated caption without Figure or Table number +} +\description{ +Used to remove the first part fo the captions. It will clean +normal captions like Figure/Table followed by its numbering (i.e. Figure 1-1, +Table 2-1), and also more complex patterns created by using Novstyle such as +"Table STYLEREF 1 \\s 2 SEQ Table \\* ARABIC \\s 1 1. Summary of Sample Statistics" +} +\examples{ +\dontrun{ +example_caption <- +"Table STYLEREF 1 \\\\s 2 SEQ Table \\\\* ARABIC \\\\s 1 1. Summary of Sample Statistics" +updated_example_caption <- remove_fig_table_part(example_caption) +expected_output <- "Summary of Sample Statistics" +} +} diff --git a/man/replace_text.Rd b/man/replace_text.Rd new file mode 100644 index 0000000..7199d21 --- /dev/null +++ b/man/replace_text.Rd @@ -0,0 +1,23 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{replace_text} +\alias{replace_text} +\title{Replace text with officer} +\usage{ +replace_text(doc, old_value, new_value) +} +\arguments{ +\item{doc}{Docx document to be modified} + +\item{old_value}{Word to search in the doc} + +\item{new_value}{Replacement for the old_value} +} +\description{ +Search for a value in a document and replicate it with the new one +} +\examples{ +\dontrun{ +replace_text("dummy_doc.docx", "title", "new title") +} +} diff --git a/man/rmd_render.Rd b/man/rmd_render.Rd new file mode 100644 index 0000000..f259482 --- /dev/null +++ b/man/rmd_render.Rd @@ -0,0 +1,43 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/rmd_render.R +\name{rmd_render} +\alias{rmd_render} +\title{Rmd Sample Size renderer} +\usage{ +rmd_render( + rmd_filename, + output_path = dirname(rmd_filename), + version = 0, + reference_docx = generic_report_template() +) +} +\arguments{ +\item{rmd_filename}{String. Name of the Rmd file to be rendered.} + +\item{output_path}{String. Set the path where your generated Word file will be written to. +The default path will the path of the Rmd file.} + +\item{version}{Integer. Version number of the created Word file, e.g., 0, 1, 12. +Will be modified to follow version numbering guidelines, e.g., _v00, _v01, v_12.} + +\item{reference_docx}{String. Template document in .docx format providing +styling and structure information for rendering your docx report. +This is configured to be the generic report template using the function +\code{generic_report_template()}.} +} +\value{ +Word docx file rendered from the Rmd located in \code{output_path} +} +\description{ +Given the filename of a generic report Rmd template, it renders it. +It is a wrapper that hides the process of rendering, starting by getting the +title and body separately, getting the study code and compound name, and merging +both files with the correct output name. +} +\examples{ +\dontrun{ +rmd_render(system.file("use_cases/01_generic_report", + "generic_report_template.Rmd", + package="rdocx")) +} +} diff --git a/man/save_updated_document.Rd b/man/save_updated_document.Rd new file mode 100644 index 0000000..652d47c --- /dev/null +++ b/man/save_updated_document.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{save_updated_document} +\alias{save_updated_document} +\title{Save updated document} +\usage{ +save_updated_document(document, doc_final_path) +} +\arguments{ +\item{document}{Officer doc to be saved} + +\item{doc_final_path}{Location to save} +} +\description{ +Save updated document +} +\examples{ +\dontrun{ +save_updated_document(doc, "./new_doc.docx") +} +} diff --git a/man/search_for_duplicates.Rd b/man/search_for_duplicates.Rd new file mode 100644 index 0000000..366ab34 --- /dev/null +++ b/man/search_for_duplicates.Rd @@ -0,0 +1,15 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{search_for_duplicates} +\alias{search_for_duplicates} +\title{Search for duplicates} +\usage{ +search_for_duplicates(yaml_file) +} +\arguments{ +\item{yaml_file}{List. Already read yaml file to be checked for duplicates} +} +\description{ +Look for duplicates in captions titles and raise a warning if +the occurrence parameter is not defined. +} diff --git a/man/table_styling.Rd b/man/table_styling.Rd new file mode 100644 index 0000000..a4d957d --- /dev/null +++ b/man/table_styling.Rd @@ -0,0 +1,24 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{table_styling} +\alias{table_styling} +\title{Add Generic Report Style to a flextable} +\usage{ +table_styling(flex_table, widths = NA) +} +\arguments{ +\item{flex_table}{A flextable to modify style} + +\item{widths}{If there is a specified width for the columns} +} +\value{ +Flextable with Generic Report Style +} +\description{ +Add Generic Report Style to a flextable +} +\examples{ +\dontrun{ +table_styling(table, widths = NA) +} +} diff --git a/man/version_number.Rd b/man/version_number.Rd new file mode 100644 index 0000000..f819eaa --- /dev/null +++ b/man/version_number.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils.R +\name{version_number} +\alias{version_number} +\title{Version number} +\usage{ +version_number(version) +} +\arguments{ +\item{version}{Number. The current version of the document} +} +\value{ +formatted version "_v00", "_v02", _v13" +} +\description{ +Version number +} +\examples{ +\dontrun{ +version_number(5) +} +} diff --git a/tests/testthat.R b/tests/testthat.R new file mode 100644 index 0000000..3eb6aa8 --- /dev/null +++ b/tests/testthat.R @@ -0,0 +1,12 @@ +# This file is part of the standard setup for testthat. +# It is recommended that you do not modify it. +# +# Where should you do additional test configuration? +# Learn more about the roles of various files in: +# * https://r-pkgs.org/tests.html +# * https://testthat.r-lib.org/reference/test_package.html#special-files + +library(testthat) +library(rdocx) + +test_check("rdocx") diff --git a/tests/testthat/test_ActivityTable.R b/tests/testthat/test_ActivityTable.R new file mode 100644 index 0000000..a42e974 --- /dev/null +++ b/tests/testthat/test_ActivityTable.R @@ -0,0 +1,61 @@ + +# define a class +at <- ActivityTable$new( + activity = 'Super cool calculation', + main_author_name = 'David Bowie', + main_activity_date = '01-Oct-2024', + qc_author_name = 'Freddie Mercury', + qc_activity_date = '20-Oct-2024') + +# test 1: class was initialised +test_that("ActivityTable class is initialised correctly", { + expect_s3_class(at, c("ActivityTable", "R6"), exact = TRUE) +}) + +# test 2a: class doesn't accept dates that are not formatted correctly +at_date_error <- at$clone(deep = TRUE) + +test_that("ActivityTable throws an error if the dates are not in the specified format", { + expect_no_error(at_date_error$main_activity_date <- "01-Oct-2023") + expect_equal(at_date_error$main_activity_date, "01-Oct-2023") + expect_error(at_date_error$main_activity_date <- "01-10-2023") + expect_no_error(at_date_error$qc_activity_date <- "20-Oct-2023") + expect_equal(at_date_error$qc_activity_date, "20-Oct-2023") + expect_error(at_date_error$qc_activity_date <- "20-10-2023") +}) + +# test 3: class doesn't accept names that are not formatted correctly +at_main_author_name <- at$clone(deep = TRUE) + +test_that("ActivityTable throws an error if the names are not in the format Firstname Lastname, i.e. two names with a space inbetween.", { + expect_error(at_main_author_name$main_author_name <- "Beyonce") #our class does should not accept single names + expect_no_error(at_main_author_name$main_author_name <- "Sarah Michelle Geller") #our class should accept triple names + expect_equal(at_main_author_name$main_author_name, "Sarah Michelle Geller") + expect_no_error(at_main_author_name$main_author_name <- "Sophie Ellis-Bexter") #our class should accept double-barrel names + expect_equal(at_main_author_name$main_author_name, "Sophie Ellis-Bexter") +}) + +# test 4: class produces a flextable object via the get_table() method +test_that("Rendered table is a flextable object", { + expect_true(attr(at$get_table(), "class") == "flextable") +}) + +at_activity_name <- at$clone(deep = TRUE) +test_that("ActivityTable throws an error if activity is not a string.", { + expect_error(at_activity_name$activity <- 39863) #our class does should not accept numbers + expect_no_error(at_activity_name$activity <- "Calculation") #our class should accept single words + expect_equal(at_activity_name$activity, "Calculation") + expect_no_error(at_activity_name$activity <- "A complex activity") #our class should accept multiple words + expect_equal(at_activity_name$activity, "A complex activity") +}) + +# Test 5: class assigns correct values to attributes +test_that("ActivityTable returns correct values for params", { + expect_equal(at$activity, 'Super cool calculation') + expect_equal(at$main_author_name,'David Bowie') + expect_equal(at$main_activity_date, '01-Oct-2024') + expect_equal(at$qc_author_name, 'Freddie Mercury') + expect_equal(at$qc_activity_date, '20-Oct-2024') +}) + + diff --git a/tests/testthat/test_ChangelogTable.R b/tests/testthat/test_ChangelogTable.R new file mode 100644 index 0000000..50da070 --- /dev/null +++ b/tests/testthat/test_ChangelogTable.R @@ -0,0 +1,104 @@ +# Define an example of ChangelogTableRow +ctr <- ChangelogTableRow$new( + date = '01-Oct-2024', + version = 'Initial version', + why_update = 'Create first version of the document', + what_changed = 'NA/ Initial version') + +# Test 0: class assigns correct values to attributes +test_that("ChangelogTableRow returns correct values for params", { + expect_equal(ctr$date, '01-Oct-2024') + expect_equal(ctr$version,'Initial version') + expect_equal(ctr$why_update, 'Create first version of the document') + expect_equal(ctr$what_changed, 'NA/ Initial version') +}) + +# Test 1: class was initialized +test_that("ChangelogTableRow class is initialised correctly", { + expect_s3_class(ctr, c("ChangelogTableRow", "R6"), exact = TRUE) +}) + +# Test 2: date error +ctr_date_error <- ctr$clone(deep = TRUE) + +test_that("ChangelogTableRow throws an error if the date is not in the specified format", { + expect_error(ctr_date_error$date <- "01-10-2023") + expect_error(ctr_date_error$date <- "20-10-2023") + expect_error(ctr_date_error$date <- "20-N-2023") + expect_no_error(ctr_date_error$date <- "20-Jan-2023") + expect_equal(ctr_date_error$date, "20-Jan-2023") +}) + +# Test 3: time point is a string +ctr_versiont_error <- ctr$clone(deep = TRUE) + +test_that("ChangelogTableRow throws an error if the version is not a string", { + expect_error(ctr_versiont_error$version <- 3349855) + expect_no_error(ctr_versiont_error$version <- "Start protocol") + expect_equal(ctr_versiont_error$version, "Start protocol") +}) + +# Test 4 : why_update has more than one word +ctr_why_update_error <- ctr$clone(deep = TRUE) + +test_that("ChangelogTableRow throws an error if the why_update is not a string with more than 1 word", { + expect_error(ctr_why_update_error$why_update <- "TBD") + expect_no_error(ctr_why_update_error$why_update <- "Modify table") + expect_equal(ctr_why_update_error$why_update, "Modify table") +}) + +# Test 5 : outcome update has more than one word +ctr_what_changed_error <- ctr$clone(deep = TRUE) + +test_that("ChangelogTableRow throws an error if the what_changed is not a string with more than 1 word", { + expect_error(ctr_what_changed_error$what_changed <- "TBD") + expect_no_error(ctr_what_changed_error$what_changed <- "Updated table") + expect_equal(ctr_what_changed_error$what_changed, "Updated table") +}) + + +# Define an example of ChangelogTable +ct <- ChangelogTable$new() + +# Test 7: class was initialized +test_that("ChangelogTable class is initialised correctly", { + expect_s3_class(ct, c("ChangelogTable", "R6"), exact = TRUE) +}) + +# Test 8: add_row doesn't work if the input is not from class ChangelogTableRow +ct_add_row_error <- ct$clone(deep = TRUE) + +test_that("add_row() throws an error when the input is not from class ChangelogTableRow", { + expect_error(ct_add_row_error$add_row(7)) + expect_no_error(ct_add_row_error$add_row(ctr)) + expect_equal(ct_add_row_error$rows[[1]], ctr) +}) + +# Test 9: to_dataframe creates a dataframe and it has the same number of rows as rows added +ctr_2 <- ChangelogTableRow$new( + date = '15-Oct-2024', + version = 'Final version', + why_update = 'Change calculation of param 1', + what_changed = 'Section 2') + +ct_to_dataframe_error <- ct$clone(deep = TRUE) +ct_to_dataframe_error$add_row(ctr) +ct_to_dataframe_error$add_row(ctr_2) + +test_that("add_row() saves correctly second row", { + expect_equal(ct_to_dataframe_error$rows[[2]], ctr_2) +}) + +test_that("to_dataframe() return a dataframe with same number of rows as rows added", { + expect_s3_class(ct_to_dataframe_error$to_dataframe(), "data.frame" ) + expect_equal(nrow(ct_to_dataframe_error$to_dataframe()), 2) +}) + +# Test 10: get_table returns a flextable +ct_get_table_error <- ct$clone(deep = TRUE) +ct_get_table_error$add_row(ctr) + +test_that("get_table() return a flextable", { + expect_s3_class(ct_to_dataframe_error$get_table(), "flextable" ) +}) + diff --git a/tests/testthat/test_ReplaceOutputs.R b/tests/testthat/test_ReplaceOutputs.R new file mode 100644 index 0000000..dbe16d3 --- /dev/null +++ b/tests/testthat/test_ReplaceOutputs.R @@ -0,0 +1,179 @@ +# Define an example class +uo <- ReplaceOutputs$new( + template_docx_filename = system.file("use_cases/02_automated_reporting", + "Automated_Reporting_Example.docx", + package="rdocx"), + outputs_path = system.file("use_cases/02_automated_reporting/example_outputs", + package="rdocx"), + doc_final_filename = "./test.docx", + yml_filename = system.file("use_cases/02_automated_reporting", + "example_yml_1.yml", + package="rdocx") +) + +# Test 0: class assigns correct values to attributes +test_that("ChangelogTableRow returns correct values for params", { + expect_equal(uo$template_docx_filename, + system.file("use_cases/02_automated_reporting", + "Automated_Reporting_Example.docx", + package="rdocx") + ) + expect_equal(uo$outputs_path, + system.file("use_cases/02_automated_reporting/example_outputs", + package="rdocx") + ) + expect_equal(uo$doc_final_filename, "./test.docx") + expect_equal(uo$yml_filename, + system.file("use_cases/02_automated_reporting", + "example_yml_1.yml", + package="rdocx") + ) +}) + +# Test 1: class was initialized +test_that("`ReplaceOutputs` class is initialised correctly", { + expect_s3_class(uo, c("ReplaceOutputs", "R6"), exact = TRUE) +}) + +# Test 2: template docx path error +uo_template_docx_filename_error <- uo$clone(deep = TRUE) + +test_that("`ReplaceOutputs` throws an error if the `template_docx_filename` is mispecified", + { + expect_error( + uo_template_docx_filename_error$template_docx_filename <- + "example_outputs/parameter_estimates.csv" + ) + expect_error( + uo_template_docx_filename_error$template_docx_filename <- + "this_doc_does_not_exist.docx" + ) + expect_no_error( + uo_template_docx_filename_error$template_docx_filename <- + system.file( + "use_cases/02_automated_reporting", + "Automated_Reporting_Example_with_sources.docx", + package = + "rdocx" + ) + ) + expect_equal( + uo_template_docx_filename_error$template_docx_filename, + system.file( + "use_cases/02_automated_reporting", + "Automated_Reporting_Example_with_sources.docx", + package = + "rdocx" + ) + ) + }) + +# Test 3: outputs path error +uo_outputs_path_error <- uo$clone(deep = TRUE) + +test_that("`ReplaceOutputs` throws an error if the `outputs_path` is mispecified", { + expect_error(uo_outputs_path_error$outputs_path <- "path/to/nowhere/") + expect_no_error(uo_outputs_path_error$outputs_path <- system.file("use_cases/02_automated_reporting", + package="rdocx")) + expect_equal(uo_outputs_path_error$outputs_path, + system.file("use_cases/02_automated_reporting", + package="rdocx")) + expect_error(uo_outputs_path_error$outputs_path <- NA) +}) + +# Test 4: doc final path error +uo_doc_final_filename_error <- uo$clone(deep = TRUE) + +test_that("`ReplaceOutputs` throws an error if the `doc_final_filename` is mispecified", { + expect_error(uo_doc_final_filename_error$doc_final_filename <- "path/to/nowhere/") + expect_no_error(uo_doc_final_filename_error$doc_final_filename <- "./test_1.docx") + expect_equal(uo_doc_final_filename_error$doc_final_filename, "./test_1.docx") +}) + +# Test 5: yml path error +uo_yml_filename_error <- uo$clone(deep = TRUE) + +test_that("`ReplaceOutputs` throws an error if the `yml_filename` is mispecified", { + expect_error(uo_yml_filename_error$yml_filename <- "example_outputs/parameter_estimates.csv") + expect_error(uo_yml_filename_error$yml_filename <- "this_doc_does_not_exist.docx") + expect_no_error(uo_yml_filename_error$yml_filename <- system.file("use_cases/02_automated_reporting", + "example_yml_1.yml", + package="rdocx")) + expect_equal(uo_yml_filename_error$yml_filename, system.file("use_cases/02_automated_reporting", + "example_yml_1.yml", + package="rdocx")) + expect_no_error(uo_yml_filename_error$yml_filename <- NA) + expect_equal(uo_yml_filename_error$yml_filename, NA) +}) + + +# Test 6: update_all_outputs generates correclt a docx +test_that("`ReplaceOutputs` 'update_all_outputs()' method creates a docx correclty", { + uo$update_all_outputs() + expect_true(file.exists("./test.docx")) + file.remove("./test.docx") + expect_true(file.exists("./test.log")) + file.remove("./test.log") +}) + +# Test 7: get_captions_to_yml creates a yml file +uo_yml <- ReplaceOutputs$new( + template_docx_filename = system.file("use_cases/02_automated_reporting", + "Automated_Reporting_Example_with_sources.docx", + package="rdocx"), + outputs_path = system.file("use_cases/02_automated_reporting/example_outputs", + package="rdocx"), + doc_final_filename = "./test.docx" +) + +test_that("`ReplaceOutputs` 'get_captions_to_yml()' method creates a yml correclty", { + uo_yml$get_captions_to_yml("./test.yml") + expect_true(file.exists("./test.yml")) + +}) +# Test 8: get_captions_to_yml produces expected yml file +test_that("`ReplaceOutputs` 'get_captions_to_yml()' method creates a yml correclty", { + uo_yml$get_captions_to_yml("./test.yml") + rdocx_yml <- yaml::read_yaml("./test.yml") + comparison_yml <- yaml::read_yaml("test_get_captions_to_yml.yml") + expect_equal(rdocx_yml, comparison_yml) + file.remove("./test.yml") +}) + + +uo_2 <- ReplaceOutputs$new( + template_docx_filename = system.file("use_cases/02_automated_reporting", + "Automated_Reporting_Example.docx", + package="rdocx"), + outputs_path = system.file("use_cases/02_automated_reporting/example_outputs", + package="rdocx"), + doc_final_filename = "./test.docx", + yml_filename = "test_unmodified_doc.yml" + ) + +test_that("`ReplaceOutputs` produces warning if the document is not updated", { + expect_output( + uo_2$update_all_outputs(), + regexp = "Warning: The docx was not modified!" + ) + }) + + uo_3 <- ReplaceOutputs$new( + template_docx_filename = system.file("use_cases/02_automated_reporting", + "Automated_Reporting_Example.docx", + package="rdocx"), + outputs_path = system.file("use_cases/02_automated_reporting/example_outputs", + package="rdocx"), + doc_final_filename = "./test.docx", + yml_filename = "test_duplicated_fig_names.yml" + ) + +test_that("`ReplaceOutputs` produces warning if duplicated fig names in yml", { + expect_warning( + uo_3$update_all_outputs(), + regexp = "Duplicated title" + ) + }) + +file.remove(list.files("./", pattern = "test.log", full.names = TRUE)) +file.remove(list.files("./", pattern = "test.docx", full.names = TRUE)) diff --git a/tests/testthat/test_SignaturesTable.R b/tests/testthat/test_SignaturesTable.R new file mode 100644 index 0000000..47313d4 --- /dev/null +++ b/tests/testthat/test_SignaturesTable.R @@ -0,0 +1,95 @@ +str <- SignaturesTableRow$new( + name = 'Harry Styles', + department = 'eCompliance', + date = '15-Feb-2024') + +# Test 0: class assigns correct values to attributes +test_that("SignaturesTableRow returns correct values for params", { + expect_equal(str$name, 'Harry Styles') + expect_equal(str$department,'eCompliance') + expect_equal(str$date, '15-Feb-2024') +}) + +# Test 1: class was initialized +test_that("SignaturesTableRow class is initialised correctly", { + expect_s3_class(str, c("SignaturesTableRow", "R6"), exact = TRUE) +}) + +# Test 2: name error +str_name_error <- str$clone(deep = TRUE) + +test_that("ChangelogTableRow throws an error if the date is not in the specified format", { + expect_error(str_name_error$name <- "Claudia") + expect_no_error(str_name_error$name <- "Sarah Michelle Geller") + expect_equal(str_name_error$name, "Sarah Michelle Geller") + expect_no_error(str_name_error$name <- "Sophie Ellis-Bexter") + expect_equal(str_name_error$name, "Sophie Ellis-Bexter") +}) + +# Test 3: department error +str_department_error <- str$clone(deep = TRUE) + +test_that("ChangelogTableRow throws an error if the date is not in the specified format", { + expect_error(str_department_error$department <- 387) + expect_no_error(str_department_error$department <- "Department") + expect_equal(str_department_error$department, "Department") + expect_no_error(str_department_error$department <- "Super cool department") + expect_equal(str_department_error$department, "Super cool department") +}) + + +# Test 4: date error +str_date_error <- str$clone(deep = TRUE) + +test_that("ChangelogTableRow throws an error if the date is not in the specified format", { + expect_error(str_date_error$date <- "01-10-2023") + expect_error(str_date_error$date <- "20-10-2023") + expect_error(str_date_error$date <- "20-N-2023") + expect_no_error(str_date_error$date <- "20-Jan-2023") + expect_equal(str_date_error$date, "20-Jan-2023") +}) + +# Define an example of ChangelogTable +st <- SignaturesTable$new() + +# Test 7: class was initialized +test_that("SignaturesTable class is initialised correctly", { + expect_s3_class(st, c("SignaturesTable", "R6"), exact = TRUE) +}) + +# Test 8: add_row doesn't work if the input is not from class ChangelogTableRow +st_add_row_error <- st$clone(deep = TRUE) + +test_that("add_row() throws an error when the input is not from class SignaturesTableRow", { + expect_error(st_add_row_error$add_row(7)) + expect_no_error(st_add_row_error$add_row(str)) + expect_equal(st_add_row_error$rows[[1]], str) +}) + +# Test 9: to_dataframe creates a dataframe and it has the same number of rows as rows added +str_2 <- SignaturesTableRow$new( + name = 'Alex Turner', + department = 'Product Development', + date = '20-Feb-2024') + +st_to_dataframe_error <- st$clone(deep = TRUE) +st_to_dataframe_error$add_row(str) +st_to_dataframe_error$add_row(str_2) + +test_that("add_row() saves correctly second row", { + expect_equal(st_to_dataframe_error$rows[[2]], str_2) +}) + +test_that("to_dataframe() return a dataframe with same number of rows as rows added", { + expect_s3_class(st_to_dataframe_error$to_dataframe(), "data.frame" ) + expect_equal(nrow(st_to_dataframe_error$to_dataframe()),2) +}) + +# Test 10: get_table returns a flextable +st_get_table_error <- st$clone(deep = TRUE) +st_get_table_error$add_row(str) + +test_that("get_table() return a flextable", { + expect_s3_class(st_get_table_error$get_table(), "flextable" ) +}) + diff --git a/tests/testthat/test_TitlePage.R b/tests/testthat/test_TitlePage.R new file mode 100644 index 0000000..b7e90f5 --- /dev/null +++ b/tests/testthat/test_TitlePage.R @@ -0,0 +1,186 @@ + +# Define an example class +tp <- TitlePage$new( + report_title= "Super cool document", + department = "Cool department", + study_title = "Compute super cool things", + study_number = "OWNDJQW9923", + status = "Draft", + version = "1", + date = "01-Feb-2024", + bus_class = "Very confidential", + changelog_table = NULL) + +# Test 0: class assigns correct values to attributes +test_that("ActivityTable returns correct values for params", { + expect_equal(tp$report_title, "Super cool document") + expect_equal(tp$department,"Cool department") + expect_equal(tp$study_title, "Compute super cool things") + expect_equal(tp$study_number,"OWNDJQW9923") + expect_equal(tp$status, "Draft") + expect_equal(tp$version, "1") + expect_equal(tp$date, "01-Feb-2024") + expect_equal(tp$bus_class, "Very confidential") + expect_equal(tp$changelog_table, NULL) +}) + +# Test 1: class was initialized +test_that("`TitlePage` class is initialised correctly", { + expect_s3_class(tp, c("TitlePage", "R6"), exact = TRUE) +}) + +# Test 2: report title +tp_report_title_error <- tp$clone(deep = TRUE) + +test_that("`TitlePage` throws an error if `report_title` is not in the specified format (string)", { + expect_error(tp_report_title_error$report_title <- 837432) + expect_error(tp_report_title_error$report_title <- "Document") + expect_no_error(tp_report_title_error$report_title <- "Cool Document") + expect_equal(tp_report_title_error$report_title, "Cool Document") +}) + +# Test 3: department error +tp_department_error <- tp$clone(deep = TRUE) + +test_that("TitlePage throws an error if the department is not a string", { + expect_error(tp_department_error$department <- 3349855) + expect_no_error(tp_department_error$department <- "Department") + expect_equal(tp_department_error$department, "Department") + expect_no_error(tp_department_error$department <- "Specific Department") + expect_equal(tp_department_error$department, "Specific Department") +}) + +# Test 4: study title error +tp_study_title_error <- tp$clone(deep = TRUE) + +test_that("`TitlePage` throws an error if `study_title` is not a string with spaces", { + expect_error(tp_study_title_error$study_title <- 48947) + expect_error(tp_study_title_error$study_title <- "Title_example_no_spaces") + expect_no_error(tp_study_title_error$study_title <- "This is a title with spaces") + expect_equal(tp_study_title_error$study_title, "This is a title with spaces") +}) + +# Test 5: study number error +tp_study_number_error <- tp$clone(deep = TRUE) + +test_that("`TitlePage` throws an error if `study_title` is not a string with spaces", { + expect_error(tp_study_number_error$study_number <- 48947) + expect_no_error(tp_study_number_error$study_number <- "UQIUDEB082") + expect_equal(tp_study_number_error$study_number, "UQIUDEB082") +}) + + +# Test 6: status error +tp_status_error <- tp$clone(deep = TRUE) + +test_that("`TitlePage` throws an error if `status` is not `Draft` or `Final`", { + expect_error(tp_status_error$status <- "Pending") + expect_error(tp_status_error$status <- 37402) + expect_error(tp_status_error$status <- "Fnail") + expect_no_error(tp_status_error$status <- "Draft") + expect_equal(tp_status_error$status, "Draft") + expect_no_error(tp_status_error$status <- "Final") + expect_equal(tp_status_error$status, "Final") +}) + +# Test 5: version error +tp_version_error <- tp$clone(deep = TRUE) + +test_that("`TitlePage` throws an error if `study_title` is not a string with spaces", { + expect_error(tp_version_error$version <- 3) + expect_no_error(tp_version_error$version <- "2") + expect_equal(tp_version_error$version, "2") +}) + +# Test 8: release_date not in format +tp_date_error <- tp$clone(deep = TRUE) + +test_that("`TitlePage` throws an error if the `release_date` are not in the specified format", { + expect_error(tp_date_error$date <- "01-10-2023") + expect_error(tp_date_error$date <- "20-10-2023") + expect_error(tp_date_error$date <- "20-N-2023") + expect_no_error(tp_date_error$date <- "20-Feb-2023") + expect_equal(tp_date_error$date, "20-Feb-2023") +}) + +# Test 9: bus_class error +tp_bus_class_error <- tp$clone(deep = TRUE) + +test_that("`TitlePage`` throws an error if the 'bus_class' is not a number bigger or equal to 1", { + expect_no_error(tp_bus_class_error$bus_class <- 'Confidential') + expect_error(tp_bus_class_error$bus_class <- 3298) + expect_equal(tp_bus_class_error$bus_class, 'Confidential') +}) + +# Test 10: get title page method +test_that("`TitlePage$get_title_page`' method creates a docx correclty", { + expect_no_error(tp$get_title_page()) + expect_true(file.exists("./_title.docx")) + file.remove("./_title.docx") +}) + +test_that("`TitlePage$get_title_page` throws an error if the `reference_doxc` or `output_path` are mispecified", { + expect_error(tp$get_title_page(reference_docx = "this_doc_does_not_exist.docx"), + regex = "File does not exist") + expect_no_error(tp$get_title_page(reference_docx = system.file("use_cases/00_word_templates/generic_report/", + "Generic_Report_Template.docx", + package = "rdocx"))) + file.remove("./_title.docx") + expect_error(tp$get_title_page(output_path = "path/to/nowhere/"), + regex = "does not exist") +}) + +# Test that a ChnagelogTable can be added to the TitlePage +ctr_1 <- ChangelogTableRow$new( + date = '01-Oct-2024', + version = 'Initial version', + why_update = 'Create first version of the document', + what_changed = 'NA/ Initial version') + +ctr_2 <- ChangelogTableRow$new( + date = '15-Oct-2024', + version = 'Final version', + why_update = 'Change calculation of param 1', + what_changed = 'Section 2') + +ct_example <- ChangelogTable$new() +ct_example$add_row(ctr_1) +ct_example$add_row(ctr_2) +ct_table <- ct_example$get_table() + +test_that("`TitlePage` allows to add a ChangelogTable as a param", { + expect_no_error(tp$changelog_table <- ct_table) + expect_equal(tp$changelog_table, ct_table) + expect_error(tp$changelog_table <- "table") +}) + +# Test that SignaturesTable can be added to the TitlePage +str_1 <- SignaturesTableRow$new( + name = 'Harry Styles', + department = 'eCompliance', + date = '15-Feb-2024') + +str_2 <- SignaturesTableRow$new( + name = 'Alex Turner', + department = 'Product Development', + date = '20-Feb-2024') + +st_example <- SignaturesTable$new() +st_example$add_row(str_1) +st_example$add_row(str_2) +st_table <- st_example$get_table() + +test_that("`TitlePage` allows to add a ChangelogTable as a param", { + expect_no_error(tp$signatures_table <- st_table) + expect_equal(tp$signatures_table, st_table) + expect_error(tp$signatures_table <- "table") +}) + + +test_that("generic_report_template() loads correctly the reference doc", { + expected_value <- system.file("use_cases/00_word_templates/generic_report", + "Generic_Report_Template.docx", + package = "rdocx") + expect_equal(generic_report_template(), expected_value) +}) + diff --git a/tests/testthat/test_duplicated_fig_names.yml b/tests/testthat/test_duplicated_fig_names.yml new file mode 100644 index 0000000..f77f255 --- /dev/null +++ b/tests/testthat/test_duplicated_fig_names.yml @@ -0,0 +1,9 @@ +fig_2: + type: FIG + title: "Comparative Analysis of Categories X and Y" + file: Figure_2_2_Comparative_Analysis.png + +fig_3: + type: FIG + title: "Comparative Analysis of Categories X and Y" + file: Figure_2_3_Comparative_Analysis.png \ No newline at end of file diff --git a/tests/testthat/test_get_captions_to_yml.yml b/tests/testthat/test_get_captions_to_yml.yml new file mode 100644 index 0000000..0097310 --- /dev/null +++ b/tests/testthat/test_get_captions_to_yml.yml @@ -0,0 +1,54 @@ +output_1: + type: TBL + title: Summary of Sample Statistics + file: Table_2_1_Summary_of_Sample_Statistics.csv + widths: ~ + occurrence: ~ +output_2: + type: FIG + title: Data Point Distribution in Sample Set A + file: Figure_2_1_Data_Point_Distribution.png + widths: ~ + occurrence: ~ +output_3: + type: FIG + title: Comparative Analysis of Categories X and Y + file: Figure_2_2_Comparative_Analysis.png + widths: ~ + occurrence: ~ +output_4: + type: FIG + title: Comparative Analysis of Categories X and Y + file: Figure_2_3_Comparative_Analysis.png + widths: ~ + occurrence: ~ +output_5: + type: TBL + title: Breakdown of Category X by Subcategory + file: Table_2_2_Breakdown_of_Category_X.csv + widths: ~ + occurrence: ~ +output_6: + type: FIG + title: Trend Analysis Over Time (Yearly Data) + file: Figure_2_4_Trend_Analysis.png + widths: ~ + occurrence: ~ +output_7: + type: FIG + title: Trend Analysis Over Time (Yearly Data) + file: Figure_2_5_Trend_Analysis.png + widths: ~ + occurrence: ~ +output_8: + type: TBL + title: Yearly Performance Metrics + file: Table_3_1_Yearly_Performance_Metrics.csv + widths: ~ + occurrence: ~ +output_9: + type: FIG + title: Trend Analysis Over Time (Monthly Data) + file: Figure_3_1_Trend_Analysis.png + widths: ~ + occurrence: ~ diff --git a/tests/testthat/test_rmd_render.R b/tests/testthat/test_rmd_render.R new file mode 100644 index 0000000..e7a183f --- /dev/null +++ b/tests/testthat/test_rmd_render.R @@ -0,0 +1,36 @@ +print(generic_report_template()) + +# Test 1: checks for the template docx for the sample size use case + +test_that("The global variable defining the template exists and points + to docx file in the expected format", { + expect_true(file.exists(generic_report_template())) +}) + + +rmd_filename <- system.file("use_cases/01_generic_report/", + "generic_report_template.Rmd", + package = "rdocx" +) +output_path <- dirname(rmd_filename) + +# Test 2: check that rendering runs successfully +test_that("`rmd_render` executes successfully using the example provided", { + expect_no_error(rmd_render( + rmd_filename = rmd_filename, + output_path = output_path + )) + expect_true( + file.exists( + list.files(output_path, pattern = ".docx", full.names = TRUE) + ) + ) + expect_true( + file.exists( + list.files(output_path, pattern = ".log", full.names = TRUE) + ) + ) +}) + +file.remove(list.files(output_path, pattern = ".docx", full.names = TRUE)) +file.remove(list.files(output_path, pattern = ".log", full.names = TRUE)) diff --git a/tests/testthat/test_unmodified_doc.yml b/tests/testthat/test_unmodified_doc.yml new file mode 100644 index 0000000..5fa6264 --- /dev/null +++ b/tests/testthat/test_unmodified_doc.yml @@ -0,0 +1,43 @@ +output_1: + type: TBL + title: Summary of Sample Statistics + file: ~ + widths: ~ + occurrence: ~ +output_2: + type: FIG + title: Data Point Distribution in Sample Set A + file: ~ + widths: ~ + occurrence: ~ +output_3: + type: FIG + title: Comparative Analysis of Categories X and Y + file: ~ + widths: ~ + occurrence: ~ +output_5: + type: TBL + title: Breakdown of Category X by Subcategory + file: ~ + widths: ~ + occurrence: ~ +output_6: + type: FIG + title: Trend Analysis Over Time (Yearly Data) + file: ~ + widths: ~ + occurrence: ~ +output_8: + type: TBL + title: Yearly Performance Metrics + file: ~ + widths: ~ + occurrence: ~ +output_9: + type: FIG + title: Trend Analysis Over Time (Monthly Data) + file: ~ + widths: ~ + occurrence: ~ + diff --git a/vignettes/automated_reporting.Rmd b/vignettes/automated_reporting.Rmd new file mode 100644 index 0000000..c245518 --- /dev/null +++ b/vignettes/automated_reporting.Rmd @@ -0,0 +1,226 @@ +--- +title: "Automated Reporting" +Author: Novartis +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{Automated Reporting} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>" +) +``` + +```{r, eval=FALSE} +# Install and load rdocx +library(rdocx) + +# Load other required packages +library(flextable) +library(magrittr) +``` + +# Introduction + +In this vignette we demonstrate using the `rdocx` package for updating and automatically adding tables and figures in a docx file with the following workflow: + +1. The user populates a yml file with the names of tables and figures to be updated or added. +2. The user updates the docx report by executing the appropriate `rdocx` function in the console and passing the yml file location as an argument. +3. The user inspects the docx report to ensure that the tables and figures have been updated correctly. + +❗{rdocx} has been developed to work with "Generic Report" style documents, you can find an example at `use_cases/02_automated_reporting/Automated_Reporting_Example.docx`. It assumes that the Word document will have a specific format. Therefore, unexpected behavior can be found if the following guidelines are not followed: + +- Captions or titles are expected to be on top of figures and tables + +- Captions are expected to be followed by a placeholder/temporary figure or table + +💪 {rdocx} currently supports **.csv** format for tables and **.jpeg, .png, .bmp** and **.pdf** for figures. + +# 1. Writing the yml file + +In the `rdocx` package, you can find an example of a yml file here: `use_cases/02_automated_reporting/example_yml_1` + +The yml file should have a predefined structure and parameters to be set: + +- `type`. Indicates if it is a table (TBL) or a figure (FIG) + +- `title`. The title or caption of the table/figure in the template docx file. This will be used to search for the figure/table in the template docx document, and it will remain the same in the updated docx document. + +- `file`. The name of the file (csv or image) to be used in the new version of the docx. + +- `widths`. For tables only, a pre-defined column widths (useful for model parameter table, for example). + +- `occurrence`. Integer. To be used when captions are duplicated for more than one figure/table to define the order they appear in the docx document. + +Below is an example of the yaml structure: + +``` +output_1: + type: TBL + title: "Caption of the output 1" + file: output_1.csv + +output_2: + type: FIG + title: "Caption of the output 2" + file: output_2.png + +output_3: + type: TBL + title: "Caption of the output 3" + file: output_3.csv + widths: c(0.6,0.2,0.2) + occurrence: 1 + +output_4: + type: TBL + title: "Caption of the output 3" + file: output_4.csv + occurrence: 2 +``` + +# 2. Updating the docx report + +## Replace Outputs Class + +To update figures and tables in a docx document, the `rdocx` class `ReplaceOutputs` should be used. Each attribute of the class has a restricted set of entries that are checked once the input is provided. For example, the class will make sure that the files passed as attributes exist and that they have the correct extension. + +There are four parameter that the user has to define: + +- `template_docx_filename`: String. File name of the docx file that should be updated. For example, "/path/to/file/filename.docx" + +- `outputs_path`: String. Path to the folder with the outputs (figures and tables-csvs) that will be added to the new version of the docx + +- `doc_final_filename`: String. File name of the updated version of the docx file. For example, "/path/to/file/filename_updated.docx" + +- `yml_filename`: String. File name of the yml file that will guide the replacement of the outputs (tables and figures) in the updated docx file. For example, "/path/to/file/filename.yml" + +```{r, eval=FALSE} +uo <- rdocx::ReplaceOutputs$new( + template_docx_filename = "/path/to/file/filename.docx", + outputs_path = "path/to/outputs", + doc_final_filename = "./updated_filename.docx", + yml_filename = "/path/to/file/filename.yml" + ) +``` + +```{r, eval=FALSE} +uo <- rdocx::ReplaceOutputs$new( + template_docx_filename = system.file( + "use_cases/02_automated_reporting", + "Automated_Reporting_Example.docx", + package="rdocx" + ), + outputs_path = system.file( + "use_cases/02_automated_reporting/example_outputs", + package="rdocx" + ), + doc_final_filename = "./test.docx", + yml_filename = system.file( + "use_cases/02_automated_reporting", + "example_yml_1.yml", + package="rdocx" + ) +) +``` + +## Automatic yaml file creation + +As explained above, the `ReplaceOuptuts` class needs a yml file with a predefined structure as input. This yml file can be created manually or automatically using `get_captions_to_yml()`. This method requires only the location of the automatically created yml file as input. + +To use this functionality, an element of the `ReplaceOuptuts` class has to be created first without defining the `yml_filename` parameter. After that, the method can be called, and the new yml file will be created in the specified location: + +```{r, eval=FALSE} +uo <- rdocx::ReplaceOutputs$new( + template_docx_filename = "/path/to/file/filename.docx", + outputs_path = "path/to/outputs", + doc_final_filename = "./updated_filename.docx" +) + +uo$get_captions_to_yml(yml_caption_filename="/path/to/file/filename.yml") +``` + +In the background, `get_captions_to_yml()` scans for all figures and tables in the docx file and populates the yml file with the corresponding information (`type` and `title`). If the user defines in the docx file the source of the figure or caption below the placeholder image or table ("Source:"), it will assign this information to the `file` parameter in the yml file. + +⚠️ Be sure to specify correctly the path to the source figure or table. The path will be built joining the outputs paths and the source path. + +![Example of docx document with source defined under figure placeholder.](images/source_under_caption_example.png){width="50%"} + +Once the yml file is created, the user can manually add the missing information if needed (`file`, `widhts`, `occurrence`). If no `file` is provided, the output will be skipped and will remain unchanged. + +Finally, this new yml file can be reassigned to the element of `ReplaceOutputs`: + +```{r, eval=FALSE} +uo$yml_filename <- "/path/to/file/filename.yml" +``` + +```{r, eval=FALSE} +uo <- rdocx::ReplaceOutputs$new( + template_docx_filename = system.file( + "use_cases/02_automated_reporting", + "Automated_Reporting_Example.docx", + package="rdocx" + ), + outputs_path = system.file( + "use_cases/02_automated_reporting/example_outputs", + package="rdocx" + ), + doc_final_filename = "./test.docx" +) + +uo$get_captions_to_yml(yml_caption_filename="./test.yml") + +uo$yml_filename <- "./test.yml" +``` + +## Update all outputs at once + +Once the user has created an element of the `ReplaceOuptuts` class, the method `update_all_outputs()` can be used to update all the tables and figures defined in the yml file. No parameters have to be defined and, in the background, it iterates through all the outputs in the yml file, and assigns them in the correct location in the updated docx file with the correct format. + +```{r, eval=FALSE} +uo <- rdocx::ReplaceOutputs$new( + template_docx_filename = system.file( + "use_cases/02_automated_reporting", + "Automated_Reporting_Example.docx", + package="rdocx" + ), + outputs_path = system.file( + "use_cases/02_automated_reporting/example_outputs", + package="rdocx" + ), + doc_final_filename = "./test.docx", + yml_filename = system.file( + "use_cases/02_automated_reporting", + "example_yml_1.yml", + package="rdocx" + ) +) +``` + +```{r, eval=FALSE} +uo$update_all_outputs() +``` + +# 3. Inspect the updated docx report + +You should now have rendered your docx file in the location `doc_final_filename`. **Please, review the rendering to ensure it is as expected.** + +## Logging your settings and session info + +The `update_all_outputs()` function also logs information to aid reproducibility and traceability. The log file will be written to the same location as the `doc_final_filename`. The log file will contain the following information: + +- The path to the original and final docx document + +- The outputs folder and yml file location + +- The name of the figures/tables changed, and the path of the new figure/table added + +- The path from where your R libraries are loaded (i.e. the package bundle version) + +- The R session info provided by `sessionInfo()` + +👀 If you encounter an error please [raise an issue](ADD LINK) for the {rdocx} package team. diff --git a/vignettes/generic_report.Rmd b/vignettes/generic_report.Rmd new file mode 100644 index 0000000..7f8e9da --- /dev/null +++ b/vignettes/generic_report.Rmd @@ -0,0 +1,164 @@ +--- +title: "RMarkdown to Docx: Generic Report" +Author: Novartis +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{RMarkdown to Docx: Generic Report} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>" +) +``` + +```{r} +# Install and load rdocx +library(rdocx) + +# Load other required packages +library(flextable) +library(magrittr) +``` + +# Introduction + +In this vignette we demonstrate using the `rdocx` package for creating a Generic Report Document with the following workflow: + +1. The user populates the Rmarkdown template for their report. +2. The user renders their report as a docx file that follows the style of the provided docx template. +3. The user inspects the docx report and if changes are needed the user can re-generate the report by editing the Rmarkdown template and re-rendering the docx version. + +# 1. Populating the Rmarkdown template +For illustrative purposes, a Generic Report Rmd template has been created. In the `rdocx` package, the template is stored here: + +`use_cases/01_generic_report/generic_report_template.Rmd` + +The template collects the key static elements that are common across all reports as well as examples of some dynamic elements. + +If you are new to Rmarkdown then you may find this [cheat sheet](https://www.rstudio.com/wp-content/uploads/2015/02/rmarkdown-cheatsheet.pdf) helpful. + +## Static elements + +In the Rmarkdown template the following static elements are supported: + +- Title Page +- Change log table +- Signatures table +- Activity table + +Each of these elements are represented as classes in the `rdocx` package. Below is an example of how these are populated in the Rmarkdown template for the title page element. Each attribute of the element has a unique entry that goes through a series of checks once entered. For example, dates are checked to ensure they are in `dd-Mmm-yyyy` format. If the package encounters an unexpected entry the user is alerted through an error message. + +```{r} +# === Provide information for the title page === +tp <- rdocx::TitlePage$new( + report_title= "Super cool document", + department = "Cool department", + study_title = "Compute super cool things", + study_number = "OWNDJQW9923", + status = "Draft", + version = "1", + date = "01-Feb-2024", + bus_class = "Confidential" +) + +``` + +## Custom elements + +### Tables and Figures + +A report can also contain dynamic elements such as tables and figures from your analysis. These can be included by adding your R code that generates these outputs directly to the Rmarkdown file. For example, the following code will create a formatted table using the `flextable` package. + +```{r example-table-vig, out.width='\\textwidth'} +# Generate example data +example_data <- data.frame( + column_1 = seq(12), + column_2 = rnorm(12, mean = 10, sd = 1), + column_3 = paste0(round(rbeta(12, 1, 1), 2)*100, "%") +) +# Make a table +example_data %>% + flextable::flextable() %>% + flextable::width(width = 2) %>% + # Make headers bold + flextable::bold(bold = TRUE, part = "header") %>% + # Use zebra theme for row colors + flextable::theme_zebra() %>% + # Define column names + flextable::set_header_labels( + column_1 = "Header 1", + column_2 = "Header 2", + column_3 = "Header 3" + ) %>% + # Set borders + flextable::border_outer() %>% + flextable::border_inner_h() %>% + flextable::border_inner_v() %>% + # Add table captions + flextable::set_caption( + caption = "Table 1-2: This is an example table. Add caption text here.", + style = "Table Caption", autonum = "autonum" + ) +``` + + +### Text +Descriptive text can be added to the Rmarkdown using markdown syntax. + +# 2. Rendering your docx file + +Now that the Rmarkdown template has been populated with your analysis you can begin the process of rendering your docx file. This process will use the Generic Report Template as the reference document; this document provides the title page structure and the layout style for rendering the docx file. +Note that the path to the Generic Report Template is configured using the function \code{generic_report_template()}. + +If you'd like to get more information on the Generic Report Template version, please call the following function: + +```{r} +generic_report_template() +``` + +Let's render! + +```{r,eval=TRUE} +# Template Rmarkdown file that include the details for your sample size report. +rmd_filename <- system.file( + "use_cases/01_generic_report/", + "generic_report_template.Rmd", + package = "rdocx" +) + +# Specify the location where you would like your docx to be written. +# Here we use the same location as the Rmarkdown template. +output_path <- dirname(rmd_filename) + +# Define the version (e.g., 0, 1, 12 which will be modified to follow version +# Numbering guidelines, e.g., _v00, _v01, v_12.) of your report that will be used +# to name your docx file +version <- 2 + +rdocx::rmd_render( + rmd_filename = rmd_filename, + output_path = output_path, + version = version +) + +``` + +You should now have rendered your docx file in the location `output_path`. Please review the rendering to ensure it is as expected. + +## Logging your settings and session info + +The `rmd_render` function also logs information to aid reproducibility and traceability. The log file will be written to the same location as the `output_path`. The log file will contain the following information: + +- The template used + +- The name and location of your rendered docx file + +- The path from where your R libraries are loaded (i.e. the package bundle version) + +- The R session info provided by `sessionInfo()` + +👀 If you encounter an error please [raise an issue](ADD LINK) for the {rdocx} package team. diff --git a/vignettes/images/source_under_caption_example.png b/vignettes/images/source_under_caption_example.png new file mode 100644 index 0000000..b40d444 Binary files /dev/null and b/vignettes/images/source_under_caption_example.png differ