Unable to download .xpt file using xportr package
I am able to write a valid xpt file (that SAS can read) when I use the following code
library(xportr)
library(tibble)
# Create an example data frame
df_xp <- tibble(
ID = c(1, 2, 3),
Age = c(21, 32, 45),
Gender = c("Male", "Female", "Male")
)
names(df_xp) <- toupper(names(df_xp))
# Define a metadata data frame with desired attributes
metadata <- tibble(
variable = toupper(c("ID", "AGE", "GENDER")),
label = c("Subject Identifier", "Age at Visit", "Gender of Subject"),
length = c(8, 8, 20)
)
# Apply attributes using xportr functions
dfxp <- df_xp %>%
xportr_label(metadata = metadata) %>% # Assign labels
xportr_length(metadata = metadata) # Set specified lengths
# Write to an XPT file
xportr_write(dfxp, "egxportr.xpt")
However, the same does not work in a shiny app.
library(shiny)
library(haven) # For reading .xpt and writing .sas7bdat
library(readr) # For reading .csv
library(readxl) # For reading .xlsx
library(bslib)
library(jsonlite) # ADDED: For writing JSON files
library(xportr)
library(tools)
library(tibble)
library(DT)
# --- Utility Function to Read Data ---
# This function determines the file type and reads the data accordingly.
read_uploaded_file <- function(file_path, file_ext) {
data <- NULL
if (file_ext == "csv") {
# Read CSV files
data <- read_csv(file_path, show_col_types = FALSE)
} else if (file_ext == "sas7bdat") {
# Read SAS Transport (XPT) files
data <- haven::read_sas(file_path)
} else if (file_ext == "xpt") {
# Read SAS Transport (XPT) files
data <- read_xpt(file_path)
} else if (file_ext == "xlsx" || file_ext == "xls") {
# Read Excel files (assuming the first sheet for simplicity)
data <- read_excel(file_path, sheet = 1)
} else {
# Return an error message for unsupported types
stop("Unsupported file type selected.")
}
return(data)
}
ui <- page_fluid(
theme = bs_theme(version = 5, preset = "darkly"), # Use a clean, professional theme
title = "File Format Converter",
tags$h2("Data Format Converter (.sas7bdat, .csv, .xlsx to .xpt)"),
layout_columns(
col_widths = c(4, 8),
# 1. File Upload Card (Left Column)
card(
card_header("Upload File"),
fileInput(
"upload",
"Choose a file (.xpt, .csv, or .xlsx)",
multiple = FALSE,
accept = c(".xpt", ".csv", ".xlsx", ".xls", ".sas7bdat") # Accept all three types
),
# Button to trigger the download of the converted file
uiOutput("download_button_ui")
),
# 2. Preview and Status Card (Right Column)
card(
card_header("Data Preview & Status"),
# Display any error/status messages
textOutput("status_message"),
# Display the first few rows of the uploaded data
DT::dataTableOutput("data_preview")
)
)
)
# --- Shiny Server ---
server <- function(input, output, session) {
# Reactive value to store the uploaded data frame
uploaded_data <- reactiveVal(NULL)
# Reactive expression to process the uploaded file
observeEvent(input$upload, {
file_info <- input$upload
# Guard clause: check if a file was uploaded
req(file_info)
# Extract file extension
file_ext <- tolower(tools::file_ext(file_info$name))
# Reset data and status
uploaded_data(NULL)
output$status_message <- renderText("")
tryCatch({
# Call the utility function to read the data
data <- read_uploaded_file(file_info$datapath, file_ext)
# Store the data in the reactive value
uploaded_data(data)
output$status_message <- renderText(
paste("Success! File '", file_info$name,
"' loaded. Ready for conversion to .sas7bdat and .json.")
)
}, error = function(e) {
# Display error message if reading fails
uploaded_data(NULL)
output$status_message <- renderText(paste("Error reading file:", e$message))
})
})
# Render UI for the download button (only if data is available)
output$download_button_ui <- renderUI({
if (!is.null(uploaded_data())) {
tagList(
downloadButton("download_xpt", "Download as .xpt", class = "btn-success", style = "margin-left: 10px;")
)
}
})
# Render the data preview table
output$data_preview <- DT::renderDataTable({
req(uploaded_data())
# Display only the first 10 rows for preview
DT::datatable(head(uploaded_data(), 10), options = list(dom = 't'))
})
dfxpt <- reactive({
req(uploaded_data())
dfx <- uploaded_data()
labels <- c()
lengths <- c()
for (var in names(dfx)) {
mylabel <- ifelse(is.null(attr(dfx[[var]], "label")), var, attr(dfx[[var]], "label"))
labels <- c(labels, mylabel)
if (is.character(dfx[[var]])) {
lengths <- c(lengths, max(nchar(dfx[[var]]), na.rm = TRUE))
} else lengths <- c(lengths,8)
}
# Create the new filename based on the uploaded file name
original_name <- input$upload$name
df_name <- tools::file_path_sans_ext(original_name)
# Define a metadata data frame with desired attributes
metadata <- tibble(
dataset = df_name,
variable = toupper(names(dfx)),
label = labels,
length = lengths
)
# Apply attributes using xportr functions
dfxp <- dfx %>%
xportr_label(metadata = metadata) %>% # Assign labels
xportr_length(metadata = metadata) # Set specified lengths
dfxp
})
output$download_xpt <- downloadHandler(
filename = function() {
# Create the new filename based on the uploaded file name
original_name <- input$upload$name
base_name <- tools::file_path_sans_ext(original_name)
paste0(base_name, ".xpt")
},
content = function(file) {
req(dfxpt())
df <- dfxpt()
original_name <- input$upload$name
df_name <- tools::file_path_sans_ext(original_name)
temp_dir <- tempdir()
xpt_filename <- paste0(df_name, ".xpt") ######### temp
xpt_filepath <- file.path(temp_dir, xpt_filename) ######### temp
# haven::write_xpt(df, file) # SAS cannot read this file - states not a SAS dataset
# xportr::xportr_write(df, path = file) # Warning: Error in : Assertion on 'path' failed: .df file name must be 8 characters or less..
xportr::xportr_write(df, path = xpt_filepath)
} #, contentType = "application/x-sas-xport"
)
}
shinyApp(ui, server)
Any idea how to fix it?