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

IT技术 javascript html drag-and-drop textarea fileapi
2021-01-20 03:30:19

我编写了以下代码来使用 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获取绑定,一旦文件浏览?

也可以类似Java的FileReader检查系统的Readonly属性吗?File.canread()

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

3个回答

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

当在<input type="file"> Choose FileBrowse...UI处选择了文件时,在本地文件系统中删除文件不应影响调用返回File对象2.9.2。可传输对象6.7.3 DataTransfer 接口FileList.files

4. Blob 接口和二进制数据

每个都Blob必须有一个内部快照 state,它必须最初设置为底层存储的状态(如果存在任何此类底层存储),并且必须通过 保存 structured clonesnapshot state可以找到Files 的进一步规范定义

2.9.8 Blob 和 FileList 对象的 Monkey 补丁

这个猴子补丁将在适当的时候删除。请参阅w3c/FileAPI 问题 32

Blob对象是cloneable objects.

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

  2. 如果是closed,则抛出一个."DataCloneError" DOMException

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

FileList对象是可克隆的对象

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

  1. output成为targetRealm 中的一个新FileList对象

  2. 对于每一个文件,,补充的吗?[StructuredClone][15](_file, targetRealm, memory_)输出File对象列表的末尾

返回输出


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

在 chrome 中,如果在本地文件系统中为文件设置了只读权限,并且用户在用于读取文件的<input type="file">元素处选择文件,则在 chrome 处FileReader抛出错误FileReader,由FileReader progress事件生成

如果 aBlob URL设置为相同的文件对象,则blob:URL 将不会在请求时将只读文件返回给Blob URL.

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

铬、铬

在铬,其中铬webkitdirectory属性被设置和文件夹被选择具有只读许可FileList .lengthevent.target.files返回0; event.target.files.webkitGetAsEntry()未被调用,"No file chosen"在 处呈现<input type="file"> shadowDOM当文件夹放置在<input type="file">droppable属性设置的元素时,只读文件夹的目录.name.path显示在drop event.dataTransfer

当用户将文件或文件夹放置<textarea>在没有drop附加beforeunload事件的元素上时,将调用事件并在 UI 上显示提示

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

火狐

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

如果用户单击"Choose file..." <input>并选择了没有设置只读权限的文件夹,则单击文件管理器中的文件夹时,将列出该文件夹中的文件。

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

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

错误,安全问题

*nix 操作系统

当用户<textarea>在没有drop附加事件的元素上放置文件夹时,将file:公开用户文件系统协议中文件夹的完整路径文件夹中包含的文件的路径也未设置为.value; 例如,

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

当一个文件在下降<textarea>元件,其中不drop附连时,在用户文件系统中的文件的完整路径被设置为.value<textarea>; 那是,

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

如果选择了多个文件,并在下降<textarea>元素,所有的完整的文件路径设置为.value<textarea>,通过新行字符划定\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

当设置为.src一个的<img>与元素.crossOrigin集合到"anonymous"img error事件处理程序被调用

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

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

规格

4.10.5.1.18。文件上传状态 ( type=file)

例 16

由于历史原因,valueIDL 属性在文件名前加上字符串“ C:\fakepath\”。一些旧的用户代理实际上包含了完整路径(这是一个安全漏洞)。因此value,以向后兼容的方式IDL 属性获取文件名并非易事。

4.10.5.4. 公共<input>元素 API

文件名

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

注意:这个“fakepath”要求是历史上的一个可悲的意外。有关更多信息,请参阅文件上传状态部分中的示例

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

4.10.5.1.18。文件上传状态 ( type=file)

路径组件

<input>元素的type属性处于File Upload 状态时,本节中的规则适用。

所述<input>元件represents的列表selected files,每个文件由一个文件名,文件类型,以及文件主体(该文件的内容)。

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

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


在数据 URI 的 <textarea> 处删除文件

遵循Neal Deakin在错误报告中的评论

我认为提到的步骤是:

  1. 打开数据:文本/html,
  2. 从桌面拖一个文件到textarea

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

上面的预感是正确的;Linux 也将数据作为 url 和纯文本包含在内。

在 Firefox 的data:prototcoldata URI和 chrome、chrome 中删除文件

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

火狐

文件或文件夹设置为全路径名.value<textarea>

铬、铬

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

plnkr http://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


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

<textarea><p>element替换element以显示调用load事件的结果.readAsText()

要显示.resultFileReaderclickbutton元件,组可变textreader.resultload的事件FileReaderclick事件在button一组.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>

2021-03-18 03:30:19
,感谢您的响应。但除此之外,如何在浏览后和 adddoc() 之前在本地文件系统中删除文件不会影响 reader.result。它是否创建了任何缓存?
2021-04-11 03:30:19
@sushmithaP 在 Firefox 中发现了一个错误,其中文件在本地文件系统中文件的完整路径的<textarea>元素集.value<textarea>被丢弃。
2021-04-12 03:30:19
@sushmithaP At html <input type="file">element idis "myfile", at javascriptyou reference #myFile,它没有出现html在问题中。此外,使用change附加到<input type="file">元素的事件来处理从本地文件系统中选择的文件。
2021-04-14 03:30:19

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

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

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

句法

instanceOfFileReader.readAsText(blob[, encoding]);

参数

斑点

要从中读取的 Blob 或文件。

编码可选

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

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

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

F.lastModified 设置为 d。

在 MDN 上查看更多关于浏览器兼容性和FileReaderFilereadAsText 的详细文档以及FileApi 的这个W3C 草案

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

改用这个:-

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>