如何使用闪亮持久保存文本文件中的更改?

时间:2017-03-24 09:13:10

标签: r shiny

我有一个小小的闪亮应用程序用于注释文本文件。

  1. 用户界面提供fileInput来选择.txt个文件。启动应用程序时,其中一个文件是默认文件。
  2. NextPrevious按钮允许用户一次显示一个文件的内容。
  3. 用户可以选择句子中的任何文本,然后单击Add Markup按钮来注释句子。动作按钮触发javascript函数addMarkup()
  4. 标记后显示句子。
  5. 我只在这里发布闪亮的应用代码。完整的应用代码可在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,并希望我能够按照有关持久存储的文档进行操作。但是我仍然不确定如何在标记后捕获句子。

1 个答案:

答案 0 :(得分:2)

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

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

下面,您会注意到我从使用textnode切换到仅使用内部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代码。您正在使用反应性上下文从另一个反应性上下文(sourceData拉入corpus),这似乎是不必要的。所以,我试着稍微重构一下。

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对象。然后,我们创建一个观察者,使用我们更新的Javascript输入更新reactiveValues对象。发生这种情况时,数据会永久存储,您也可以在字符串中标记多个序列(尽管我没有使用嵌套标记或删除标记做任何事情)。最后,添加了一个保存功能 - 结果字符串保存在<mark>,用于显示标记区域。

如果您加载以前标记的文件,标记将再次显示。