HTML5 File API 中的 FileReader.readAsText 如何工作?

Posted

技术标签:

【中文标题】HTML5 File API 中的 FileReader.readAsText 如何工作?【英文标题】:How FileReader.readAsText in HTML5 File API works? 【发布时间】:2017-03-02 00:02:05 【问题描述】:

我使用html5文件API编写了以下代码来检查上传的文件是否存在。

<input type="file" id="myfile">
<button type="button" onclick="addDoc()">Add Document</button>
<p id="DisplayText"></p>

以下javascript代码已映射到它如下:

function addDoc() 
  var file=document.getElementById("myFile").files[0]; //for input type=file
  var reader=new FileReader();
  reader.onload = function(e) 
  reader.readAsText(file);
  var error = reader.error;
  var texte=reader.result;
  document.getElementById("DisplayText").innerText=reader.result; /*<p id="DisplayText>*/

从本地系统浏览文件后,我尝试删除文件夹中的“已浏览”文档,然后单击addDoc()。单击按钮后,我仍然可以看到Filereader.result 不为空,并且可以显示所有内容。

有人可以解释一下Filereader 的工作原理吗? FileReader 是不是文件一被浏览就被绑定了?

也可以用FileReader检查系统的Readonly属性是否类似于JavaFile.canread()

有人可以就此提出建议吗?我有IE11来测试代码。

【问题讨论】:

【参考方案1】:

FileReader load 事件异步设置 .result 值。要访问.result,请使用loadloadend 事件。

当在&lt;input type="file"&gt;Choose FileBrowse... UI 中选择了文件时,在本地文件系统中删除文件不应影响FileList 处由.files 调用返回的File 对象。见2.9.2. Transferable objects、6.7.3 The DataTransfer interface。

4. The Blob Interface and Binary Data

每个Blob 必须有一个内部快照状态,它 必须初始设置为底层存储的状态(如果有) 这种底层存储存在,并且必须通过 structured clonesnapshot state的进一步规范定义见Files。

2.9.8 Monkey patch for Blob and FileList objects

这个猴子补丁将在适当的时候被删除。见w3c/FileAPI issue 32。

Blob 对象是cloneable objects

    每个 Blob 对象的 [[Clone]] 内部方法,给定 targetRealm 并忽略内存,必须运行以下步骤:

    如果这是closed,则抛出"DataCloneError" DOMException

targetRealm中返回一个新的this实例,对应于 相同的基础数据。

FileList 对象是cloneable objects。

每个FileList对象的[[Clone]]内部方法,给定 targetRealmmemory,必须运行这些步骤:

    output 成为 targetRealm 中的新 FileList 对象。

    对于 this 中的每个 file,添加 ? [StructuredClone][15](_file, targetRealm, memory_)File 对象列表的末尾 输出

返回输出


在 webkit 和 firefox 浏览器中选择只读文件或文件夹

在 chrome 中,如果为本地文件系统中的文件设置了只读权限,并且用户在 &lt;input type="file"&gt; 元素处选择文件,其中FileReader 用于读取文件,则会在 FileReader 处引发错误,生成自FileReaderprogress 事件。

如果将Blob URL 设置为相同的文件对象,则blob: URL 将不会根据Blob URL 的请求返回只读文件。

选择文件夹权限设置为只读的文件夹

铬、铬

在 chrome 中,设置了 webkitdirectory 属性并选择具有只读权限的文件夹 FileList .lengthevent.target.files 的 chromium 返回了 0event.target.files.webkitGetAsEntry() 未被调用,"No file chosen"&lt;input type="file"&gt; shadowDOM 处呈现。将文件夹拖放到&lt;input type="file"&gt; 或设置droppable 属性的元素时,只读文件夹的目录.name.path 将显示在drop event.dataTransfer

当用户在&lt;textarea&gt; 元素处放置文件或文件夹时,没有附加drop 事件beforeunload 事件被调用并在UI 上显示提示

Do you want to leave this site?
Changes you made may not be saved.
<Stay><Leave> // <buttons>

火狐

在 Firefox 版本 47.0b9 中,allowdirs 属性设置为 &lt;input type="file"&gt; 元素,其中用户单击 "Choose folder.." &lt;input&gt;,可以通过 .then() 访问父文件夹的文件夹 .name.path链接到event.target.getFilesAndDirectories()。递归迭代Directory条目时,不返回所选文件夹中包含的文件或文件夹;返回一个空字符串。

如果用户点击"Choose file..."&lt;input&gt;并选择了一个没有设置只读权限的文件夹,当点击文件管理器中的文件夹时,会列出该文件夹中的文件。

如果选择了设置了只读权限的文件夹,则会在 UI 显示时呈现alert() 通知

  Could not read the contents of <directory name>
  Permission denied

错误、安全问题

*nix 操作系统

当用户在&lt;textarea&gt; 元素处放置文件夹时,没有附加drop 事件,用户文件系统file: 协议处的文件夹的完整路径将被暴露。文件夹中包含的文件的路径也没有设置为.value;例如,

"file:///home/user/Documents/Document/"

当文件在&lt;textarea&gt; 元素处被删除时,其中未附加drop 事件,用户文件系统中文件的完整路径设置为.value of &lt;textarea&gt;;也就是说,

"file:///home/user/Documents/Document/MyFileFullPathDisplayedAtTextAreaValue.txt"

如果在&lt;textarea&gt;元素处选择并删除了多个文件,则所有完整文件路径都设置为.value of &lt;textarea&gt;,由换行符\n界定

"file:///home/user/Documents/Document/MyFileFullPathDisplayedAtTextAreaValue1.txt"
"file:///home/user/Documents/Document/MyFileFullPathDisplayedAtTextAreaValue2.txt"
..

为文件路径创建XMLHttpRequest() 并在console 记录错误

NS_ERROR_DOM_BAD_URI: Access to restricted URI denied

当设置为&lt;img&gt; 元素的.src.crossOrigin 设置为"anonymous" 时,将调用img error 事件处理程序

调用window.open()并在第一个参数处设置完整路径

Error: Access to '"file:///home/user/Documents/Document/MyFileFullPathDisplayedAtTextAreaValue.png"' from script denied

规格

4.10.5.1.18. File Upload state (type=file)

EXAMPLE 16

由于历史原因,value IDL 属性作为前缀 带有字符串“C:\fakepath\”的文件名。一些旧版用户代理 实际上包括完整路径(这是一个安全漏洞)。 因此,从value IDL 获取文件名 向后兼容的属性是不平凡的。

4.10.5.4. Common &lt;input&gt; element APIs

文件名

在获取时,它必须返回字符串“C:\fakepath\”,后跟selected files 列表中的第一个文件的名称(如果有),如果列表为空,则返回空字符串。在 设置,如果新值是空字符串,它必须清空列表 selected files;否则,它必须抛出一个“InvalidStateErrorDOMException.

注意:这种“虚假路径”要求是历史的不幸事故。有关更多信息,请参阅File Upload 状态部分中的示例 信息。

注意:由于selected files列表中的文件名中不允许出现path components,因此“\fakepath\”不能误认 用于路径组件。

4.10.5.1.18. File Upload state (type=file)

路径组件

&lt;input&gt; 元素的type 属性在File Upload 中时 声明,本节中的规则适用。

&lt;input&gt; 元素 represents selected files 的列表, 每个文件由文件名、文件类型和文件主体( 文件的内容)。

文件名不能包含path components,即使是 用户选择了整个目录层次结构或多个文件 来自不同目录的相同名称。 路径组件,用于 File Upload 状态的目的是文件名的那些部分 由 U+005C REVERSE SOLIDUS 字符 () 字符分隔。

错误报告https://bugzilla.mozilla.org/show_bug.cgi?id=1311823


在数据 URI 的

Neal Deakin 在错误报告中的评论

我认为提到的步骤是:

    打开数据:文本/html, 将文件从桌面拖到文本区域

我可以在 Linux 上重现这个,但不能在 Windows 或 Mac 上重现。

上面的预感是正确的; Linux 将数据包含为 url 和 明文也是如此。

data: prototcol data URI 在 firefox 和 chrome、chromium 中删除文件

data:text/html,<textarea></textarea>

火狐

文件或文件夹的完整路径名设置为.value of &lt;textarea&gt;

铬、铬

data URI 放置文件在chrome 中只有textarea 元素,chromium 用地址栏上放置的文件路径替换data URI,并在同一选项卡中加载放置的文件,用内容替换data URI删除的文件。

plnkrhttp://plnkr.co/edit/ZfAGEAiyLLq8rGXD2ShE?p=preview


html, javascript 重现上述问题

<!DOCTYPE html>
<html>

<head>
  <style>
    body 
      height: 400px;
    

    textarea 
      width: 95%;
      height: inherit;
    
  </style>

  <script>
    window.onload = function() 
      var button = document.querySelector("#myfile + button");
      var input = document.getElementById("myfile");
      var display = document.getElementById("DisplayText");
      var text = null;

      function readFullPathToFileOnUserFileSystem(e) 
        var path = e.target.value;
        console.log(path);
        var w = window.open(path, "_blank");
        var img = new Image;
        img.crossOrigin = "anonymous";
        img.onload = function() 
          document.body.appendChild(this);
        
        img.onerror = function(err) 
          console.log("img error", err.message)
        
        img.src = path;
        var request = new XMLHttpRequest();
        request.open("GET", path.trim(), true);
        request.onload = function() 
          console.log(this.responseText)
        
        request.error = function(err) 
          console.log(err.message)
        
        request.send();

      

      display.addEventListener("input", readFullPathToFileOnUserFileSystem);
      input.addEventListener("change", addDoc);
      input.addEventListener("progress", function(event) 
        console.log("progress", event)
      );
      button.addEventListener("click", handleText)

      function addDoc(event) 
        var mozResult = [];

        function mozReadDirectories(entries, path) 
          console.log("dir", entries, path);
          return [].reduce.call(entries, function(promise, entry) 
              return promise.then(function() 
                console.log("entry", entry);
                return Promise.resolve(entry.getFilesAndDirectories() || entry)
                  .then(function(dir) 
                    console.log("dir getFilesAndDirectories", dir)
                    return dir
                  )
              )
            , Promise.resolve())
            .catch(function(err) 
              console.log(err, err.message)
            )
            .then(function(items) 
              console.log("items", items);
              var dir = items.filter(function(folder) 
                return folder instanceof Directory
              );
              var files = items.filter(function(file) 
                return file instanceof File
              );
              if (files.length) 
                console.log("files:", files, path);
                mozResult = mozResult.concat.apply(mozResult, files);

              
              if (dir.length) 
                console.log(dir, dir[0] instanceof Directory, dir[0]);
                return mozReadDirectories(dir, dir[0].path || path);

               else 
                if (!dir.length) 

                  return Promise.resolve(mozResult).then(function(complete) 
                    return complete
                  )

                
              

            )
            .catch(function(err) 
              console.log(err)
            )

        ;

        console.log("files", event.target.files);
        if ("getFilesAndDirectories" in event.target) 
          return (event.type === "drop" ? event.dataTransfer : event.target)
          .getFilesAndDirectories()
            .then(function(dir) 
              if (dir[0] instanceof Directory) 
                console.log(dir)
                return mozReadDirectories(dir, dir[0].path || path)
                  .then(function(complete) 
                    console.log("complete:", complete);
                    event.target.value = null;
                  );
               else 
                if (dir[0] instanceof File && dir[0].size > 0) 
                  return Promise.resolve(dir)
                    .then(function(complete) 
                      console.log("complete:", complete);
                    )
                 else 
                  if (dir[0].size == 0) 
                    throw new Error("could not process '" + dir[0].name + "' directory" + " at drop event at firefox, upload folders at 'Choose folder...' input");
                  
                
              
            ).catch(function(err) 
              console.log(err)
            )
        

        var reader = new FileReader();
        reader.onload = function(e) 
          text = reader.result;
          console.log("FileReader.result", text);
          button.removeAttribute("disabled");
        

        reader.onerror = function(err) 
          console.log(err, err.loaded, err.loaded === 0, file);
          button.removeAttribute("disabled");
        

        reader.onprogress = function(e) 
          console.log(e, e.lengthComputable, e.loaded, e.total);
        

        reader.readAsArrayBuffer(file);

      

      function handleText() 

        // do stuff with `text`: `reader.result` from `addDoc`
        display.textContent = text;
        button.setAttribute("disabled", "disabled");
        // set `text` to `null` if not needed or referenced again
        text = null;
      
    
  </script>
</head>

<body>
  <input type="file" id="myfile" webkitdirectory directory allowdirs>
  <button type="button" disabled>Add Document</button>
  <br>
  <br>
  <textarea id="DisplayText"></textarea>
</body>

</html>

plnkr http://plnkr.co/edit/8Ovw3IlYKI8BYsLhzV88?p=preview


您可以使用附加到#myfile 元素的change 事件来处理用户的文件选择操作。

&lt;textarea&gt; 元素替换&lt;p&gt; 元素以显示来自.readAsText() 调用的load 事件的结果。

要在clickbutton 元素处显示FileReader 中的.result,请将loadloadFileReader 事件设置为reader.result 内的变量text 设置为click@6。 .textContent#DisplayText 元素到变量引用之前设置的 reader.result

<!DOCTYPE html>
<html>
  <style>
    body 
      height: 400px;
    
    textarea 
      width:95%;
      height: inherit;
    
  </style>
<head>
  <script>
    window.onload = function() 
        var button = document.querySelector("#myfile + button");
        var input = document.getElementById("myfile");
        var display = document.getElementById("DisplayText");
        var text = null;
        input.addEventListener("change", addDoc);
        button.addEventListener("click", handleText)

        function addDoc(event) 
          var file = this.files[0]
          var reader = new FileReader();      
          reader.onload = function(e) 
            text = reader.result;
            button.removeAttribute("disabled");
          

          reader.onerror = function(err) 
            console.log(err, err.loaded
                        , err.loaded === 0
                        , file);
            button.removeAttribute("disabled");
          

          reader.readAsText(event.target.files[0]);
        

        function handleText() 
          
          // do stuff with `text`: `reader.result` from `addDoc`
          display.textContent = text;
          button.setAttribute("disabled", "disabled");
          // set `text` to `null` if not needed or referenced again
          text = null; 
        
    
  </script>
</head>

<body>
  <input type="file" id="myfile" accept="text/*">
  <button type="button" disabled>Add Document</button><br><br>
  <textarea id="DisplayText"></textarea>
</body>

</html>

【讨论】:

@sushmithaP 见2.9.8 Monkey patch for Blob and FileList objects,File API ,感谢您的回复。但除此之外,如何在浏览后和 adddoc() 之前删除本地文件系统中的文件不会影响 reader.result。是否创建任何缓存? @sushmithaP 在html &lt;input type="file"&gt; 元素id"myfile",在javascript 你引用#myFile,它不会出现在问题的html。此外,使用附加到&lt;input type="file"&gt; 元素的change 事件来处理从本地文件系统中选择的文件。 @sushmithaP 在 Firefox 中发现了一个错误,其中文件在 &lt;textarea&gt; 元素处放置的文件将 .value&lt;textarea&gt; 设置为本地文件系统中文件的完整路径。【参考方案2】:

FileReader 对象允许 Web 应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用 File 或 Blob 对象指定要读取的文件或数据。

文件对象可以从作为用户使用元素选择文件的结果返回的 FileList 对象、拖放操作的 DataTransfer 对象或 HTMLCanvasElement 上的 mozGetAsFile() API 获取。

readAsText 方法用于读取指定 Blob 或 File 的内容。当读取操作完成后,readyState 变为 DONE,触发 loadend,result 属性包含文件内容为文本字符串。

语法

instanceOfFileReader.readAsText(blob[, encoding]);

参数

Blob

要从中读取的 Blob 或文件。

编码可选

一个字符串,指定用于返回数据的编码。默认情况下,如果不指定此参数,则假定为 UTF-8。

对于文件的元数据,我们可以检查文件对象F,这样: F 的可读性状态为 OPENED。 F 指 bytes 字节序列。 F.size 设置为总字节数(以字节为单位)。 F.name 设置为 n。 F.type 设置为 t。

注意:如果 ASCII 编码的字符串表示 File 对象的类型,则 File 的类型 t 被认为是可解析的 MIME 类型,当 转换为字节序列,解析时不返回 undefined MIME 类型算法 [MIMESNIFF]。

F.lastModified 设置为 d。

在 MDN 上查看更多关于浏览器兼容性和FileReader、File 和readAsText 的详细文档,还有这个W3C draft for FileApi

【讨论】:

你如何回答 OP 提出的地址问题 “文件被浏览后 FileReader 是否被绑定?我们也可以使用类似于 Java 的 FileReader 检查系统 Readonly 属性File.canread()?”? 添加了 w3c 中提到的所有可用文件属性,但仍然没有 readonly 您是否尝试过读取在文件系统中设置了只读权限的文件或文件夹? “添加了 w3c 中提到的所有可用文件属性,但仍然不存在只读” 这是问题的一部分。您的答案没有解决问题的那部分,或这些问题“从本地系统浏览文件后,我尝试在单击 addDoc() 之前从文件夹中删除“已浏览”文档。单击按钮后,我仍然可以见 Filereader.result 不为空,可以显示所有内容。”. @guest271314 我尝试了具有只读权限的文件,请查看html5rocks.com/en/tutorials/file/dndfiles 的 woking 演示。此演示显示文件中所有可用的选项和属性。我不仅可以阅读,还可以拆分“只读”文件。这些文件实际上是在作为多部分表单数据上传之前在浏览器临时目录中复制的,您可以读取“只读”文件,我们不能只是覆盖或修改它们 “我尝试了具有只读权限的文件,请查看 html5rocks.com/en/tutorials/file/dndfiles 上的 woking 演示。此演示显示了文件中所有可用的选项和属性。我是不仅可以读取,还可以拆分“只读”文件。这些文件实际上是在作为多部分表单数据上传之前复制在浏览器临时目录中的,您可以读取“只读”文件,我们不能只是覆盖或修改它们” 有趣。考虑在bugzilla.mozilla.org/show_bug.cgi?id=1311823 分享您发现的内容以及如何重现的说明【参考方案3】:

改用这个:-

function loadFileAsText()

    var fileToLoad = document.getElementById("fileToLoad").files[0];
 
    var fileReader = new FileReader();
    fileReader.onload = function(fileLoadedEvent) 
    
        var textFromFileLoaded = fileLoadedEvent.target.result;
        document.getElementById("inputTextToSave").innerText = textFromFileLoaded;
    ;
    fileReader.readAsText(fileToLoad, "UTF-8");
        <p>Select a File to Load:</p>
        <input type="file" id="fileToLoad"><button onclick="loadFileAsText()">Load Selected File</button>
            <br>
            <br>
            <br>
            <p>Text file loaded:</p>
         <p id="inputTextToSave"></p>

【讨论】:

以上是关于HTML5 File API 中的 FileReader.readAsText 如何工作?的主要内容,如果未能解决你的问题,请参考以下文章

HTML5 File API 按顺序包含多个文件,而不是一次全部

php 下 html5 XHR2 + FormData + File API 上传文件

HTML5 file API加canvas实现图片前端JS压缩并上传

HTML5 file API加canvas实现图片前端JS压缩并上传 (转载)

HTML5 FileReader读取Blob对象API详解

Html5 文件 API - BLOB 用法?