r - 如何使用 shiny 保存文本文件中的更改?

标签 r shiny

我有一个用于注释文本文件的 Shiny 小应用。

  1. UI 提供fileInput 来选择.txt 文件。其中一个文件是应用程序启动时的默认文件。
  2. NextPrevious 按钮允许用户显示文件的内容,一次一个句子。
  3. 用户可以选择句子中的任何文本,然后单击添加标记 按钮为句子添加注释。操作按钮触发 javascript 函数 addMarkup()
  4. 句子被标记后显示。

我只是在这里发布 Shiny 的应用程序代码。该应用程序的完整代码可在 github repository 上获得

  library(shiny)
  ui <- fluidPage(
  tags$head(tags$script(src="textselection.js")),
  titlePanel("Corpus Annotation Utility"),
  sidebarLayout(
    sidebarPanel(
      fileInput('fileInput', 'Select Corpus', accept = c('text', 'text','.txt')),
      actionButton("Previous", "Previous"),
      actionButton("Next", "Next"),
      actionButton("mark", "Add Markup")
    ),
    mainPanel(
     tags$h1("Sentence: "),
     htmlOutput("sentence"),
     tags$h1("Sentence marked up: "),
     htmlOutput("sentenceMarkedUp") 
    )
  )
)
server <- function(input, output) {
    sourceData <- reactive({
   corpusFile <- input$fileInput
   if(is.null(corpusFile)){
     return(readCorpus('data/news.txt'))
   }
  readCorpus(corpusFile$datapath)
  })

 corpus <- reactive({sourceData()}) 
 values <- reactiveValues(current = 1)
  observeEvent(input$Next,{
    if(values$current >=1 & values$current < length(corpus())){
      values$current <- values$current + 1
    }
  })
  observeEvent(input$Previous,{
    if(values$current > 1 & values$current <= length(corpus())){
      values$current <- values$current - 1
    }
  })
  output$sentence <- renderText(corpus()[values$current])
}
shinyApp(ui = ui, server = server)  

readCorpus() 函数如下所示:

readCorpus <- function(pathToFile){
  con <- file(pathToFile) 
  sentences <- readLines(con, encoding = "UTF-8")
  close(con)
  return(sentences)
}

我的问题是如何在注释后将句子保存到文件中?

screenshot of the app

更新: 我经历了Persistent data storage in Shiny apps ,并希望我能够遵循有关持久存储的文档。但是我仍然不确定如何在标记后捕获句子。

最佳答案

这里有两个问题 - 持久化更改,然后保存输出。我使用一些 JS 和一些 R 代码解决了这个问题。我将在 Github 上执行拉取请求以提交更广泛的代码。然而,这是它的核心。

在用于选择内容的 Javascript 中,您可以使用 Shiny.onInputChange()更新 input 的元素向量。这样做,您可以创建一个 reactiveValues语料库的项目,然后使用您界面中的输入更新它。

在下面,您会注意到我从使用文本节点切换到仅使用内部 HTML。使用节点,和 firstChild ,正如您之前所做的那样,您最终会在第一个注释之后截断句子(因为它只选择 <mark> 之前的内容。这样做似乎效果更好。

window.onload = function(){
  document.getElementById('mark').addEventListener('click', addMarkup);
}

function addMarkup(){
  var sentence = document.getElementById("sentence").innerHTML,
  selection="";
  if(window.getSelection){
    selection = window.getSelection().toString();
  }
  else if(document.selection && document.selection.type != "Control"){
    selection = document.selection.createRange().text;
  }
  if(selection.length === 0){
    return;
  }
  marked = "<mark>".concat(selection).concat("</mark>");
  result = sentence.replace(selection, marked);
  document.getElementById("sentence").innerHTML = result;
  Shiny.onInputChange("textresult",result);
}

接下来,我尝试简化您的 server.R代码。您正在使用 react 上下文从另一个 react 上下文( sourceDatacorpus )中拉取,这似乎是不必要的。因此,我尝试对其进行一些重构。

library(shiny)
source("MyUtils.R")
ui <- fluidPage(
  tags$head(tags$script(src="textselection.js")),
  titlePanel("Corpus Annotation Utility"),
  sidebarLayout(
    sidebarPanel(
      fileInput('fileInput', 'Select Corpus', accept = c('text', 'text','.txt')),
      actionButton("Previous", "Previous"),
      actionButton("Next", "Next"),
      actionButton("mark", "Add Markup"),
      downloadButton(outputId = "save",label = "Download")),
    mainPanel(
      tags$h1("Sentence: "),
      htmlOutput("sentence"))
  )
)

server <- function(input, output) {
  corpus <- reactive({
    corpusFile <- input$fileInput
    if(is.null(corpusFile)) {
      return(readCorpus('data/news.txt'))
    } else {
      return(readCorpus(corpusFile$datapath))
    }
  })

  values <- reactiveValues(current = 1)
  observe({
    values$corpus <- corpus()
  })
  output$sentence <- renderText(values$corpus[values$current])

  observeEvent(input$Next,{
    if(values$current >=1 & values$current < length(corpus())) {
      values$current <- values$current + 1
    }
  })
  observeEvent(input$Previous,{
    if(values$current > 1 & values$current <= length(corpus())) {
      values$current <- values$current - 1
    }
  })
  observeEvent(input$mark,{
    values$corpus[values$current] <- input$textresult
  })
  output$save <- downloadHandler(filename = "marked_corpus.txt",
                                 content = function(file) {

                                   writeLines(text = values$corpus,
                                              con = file,
                                              sep = "\n")
                                 })
}

现在,代码有一些变化。从文件加载基本相同。我对 isolate 的怀疑是对的- 将其替换为 observe完成了我想做的事情,而 isolate只会给你初始负载。无论如何,我们使用 observe将语料库值加载到 reactiveValues您创建的对象 - 这是为了给我们一个地方来传播对数据的更改。

我们保留前进和后退的剩余逻辑。但是,我们更改了呈现输出的方式,以便它查看 reactiveValues。目的。然后,我们创建一个更新 reactiveValues 的观察者对象与我们更新的 Javascript 的输入。发生这种情况时,数据将永久存储,您还可以在字符串中标记多个序列(尽管我没有对嵌套标记或删除标记进行任何操作)。最后,添加一个保存函数——结果字符串用<mark>保存出来。用于显示标记区域。

如果您加载以前标记过的文件,标记将再次出现。

关于r - 如何使用 shiny 保存文本文件中的更改?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42995447/

相关文章:

css - 在 Shiny 中如何控制 slider 上数字的大小?

parameters - 将 react 传递给 Flexdashboard

r - 使用标准模式在R中产生数字序列

r - ggplot2中geom_bar顶部的重叠文本

r - 在 Shiny 应用中跟踪用户事件

r - 如何让图像 iconMarker 适用于plotGoogleMaps R?

mysql - r Shiny 的日期范围输入到 sql 查询

r - 在 R 数据框中标记第一个按组

r - 确定向量是否有序

r - 如何最好地将 NA 的两个因素合并为一个变量