【问题标题】:Using a local file as a data source in JavaScript在 JavaScript 中使用本地文件作为数据源
【发布时间】:2012-11-05 21:12:01
【问题描述】:

背景:

我想制作一个仅使用 JavaScript/HTML 的“应用程序”,并且可以由浏览器直接从文件系统打开。此应用必须能够从另一个文件中读取数据。然后,我将使用 JS 对其进行解析并呈现页面。作为一个简化的例子,假设我有一个 CSV 文件(download here)

Mark Rodgers,mark.rodgers@company.com,Accounting
[...]
Melissa Jones,melissa@company.com,CEO

我希望能够使用 JS 读取文件并使用其中的数据来生成我的页面。

到目前为止我所取得的成就:

Demo(右键单击 -> “另存为”将 HTML 保存到您的计算机)。它也可以在 jsfiddle 上以半损坏的方式使用(布局已损坏,但在功能上应该仍然正确)。

只需将 CSV 文本文件拖放到拖放框中,或使用文件菜单选择文本文件,JavaScript 就会读取、解析文件并填充表格。

这依赖于 FileReader API;大部分繁重的工作都是由这个函数完成的:

function handleFileSelect(evt) {
    evt.stopPropagation();
    evt.preventDefault();

    var files = evt.target.files || evt.dataTransfer.files; // FileList object.
    var file = files[0];

    // this creates the FileReader and reads stuff as text
    var fr = new FileReader();
    fr.onload = parse;
    fr.readAsText(file);

    // this is the function that actually parses the file
    // and populates the table
    function parse()
    {
        var table = document.getElementById('emps');
        var employees = fr.result.split('\n'); var c = 0;
        for (var i in employees)
        {
            var employee = employees[i].split(',');
            if (employee.length == 3)
            {
                var row = document.createElement('tr');
                row.innerHTML = "<td>" + employee.join("</td><td>") + "</td>";
                table.appendChild(row);
                c++;
            }
        }
        document.getElementById('result').innerHTML = '<span>Added ' + c + ' employees from file: ' + file.name + '</span>';
    }
}

几乎没问题,但它不方便用户手动加载文件。理想情况下,它应该能够自动加载,但出于安全原因,目前还没有浏览器允许这样做。

解决方案要求:

  • 必须离线工作;即:它不能依赖任何在线服务。这也包括在本地机器上运行的 HTTP 服务器。我们的想法是在任何只安装了浏览器的计算机上运行它。

  • 必须在使用file:/// 协议打开页面时工作(即:硬盘驱动器上的 HTML 页面)。

  • 应该依赖第三方插件(例如:Flash、Java、shudders ActiveX)。如果页面位于file:///

  • ,我很确定这些可能无论如何都不起作用
  • 它必须能够接受任意数据。这排除了以可以使用的良好格式加载文件 像 JSON。

  • 如果它适用于 Firefox 或 Chrome 中的任何一个(理想情况下两者都适用),那就没问题了。也可以依赖实验性 API

我事先知道文件名是什么,因此可以在 HTML 本身中对其进行编码。 任何让我能够从磁盘读取文件的解决方案都很好,它不必使用 FileReader API。

因此,如果有一个巧妙的技巧可以将文件加载到页面中也很好(也许将其加载到不可见的 iframe 中并让 JS 检索内容);那也没关系。

【问题讨论】:

  • 在 Chrome 中,您应该指定命令行参数 --allow-file-access-from-files 以允许访问 file:// 方案。出于安全考虑,我不确定 Chrome 是否允许以任何其他更方便的方式执行此操作。
  • 不确定,但您可能会对 node-webkit 项目感兴趣。 github.com/rogerwang/node-webkit
  • 如果您只需要 mozilla (firefox/seamonkey) 或 chrome,只要网页是也在 file:// - 我用一个例子更新了我的答案(虽然没有检查浏览器,已经有很多例子了)。如果页面本身不在 file:// 上,我很确定它不会起作用(至少它不应该,如果它确实是一个巨大的安全漏洞,应该立即报告)
  • 你不只是使用html5离线api有什么原因吗?基本上是一样的,除了你甚至不必在电脑上保存任何东西。另外,FileSystem API 输入文件只需要提示一次。

标签: javascript html file filereader


【解决方案1】:

这是我用于 Firefox 的代码,它不是可移植的,但 可以工作

正如 OP 评论的那样,enablePrivilege() 已被弃用,这应该被认为是可用的。但是由于我的 Firefox 使用以前的配置文件仍然可以使用我的代码,所以我深入研究了prefs.js(因为about:config 隐藏了这些设置,)这里是你需要的设置,你可以让它工作。

user_pref("capability.principal.codebase.p0.granted", "UniversalXPConnect");
user_pref("capability.principal.codebase.p0.id", "file://");  // path to the html file.
user_pref("capability.principal.codebase.p0.subjectName", "");

代码如下:

var File = function(file) {
  netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
  var ios = Components.classes["@mozilla.org/network/io-service;1"]
                            .getService(Components.interfaces.nsIIOService);
  if (!File.baseURI) {
    File.baseURI = ios.newURI(location.href.substring(0, location.href.lastIndexOf('/')+1), null, null);
    File.baseFolder = File.baseURI.QueryInterface(Components.interfaces.nsIFileURL).file.path;
  }
  var URL = ios.newURI(file, null, File.baseURI);
  this.fptr = URL.QueryInterface(Components.interfaces.nsIFileURL).file;
}

File.prototype = {
  write: function(data) {
    netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
    var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
                             .createInstance(Components.interfaces.nsIFileOutputStream);
    foStream.init(this.fptr, 0x02 | 0x08 | 0x20, 0666, 0);
    var converter = Components.classes["@mozilla.org/intl/converter-output-stream;1"]
                              .createInstance(Components.interfaces.nsIConverterOutputStream);
    converter.init(foStream, null, 0, 0);
    converter.writeString(data);
    converter.close();
  },
  read: function() {
    netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
    var fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]
                            .createInstance(Components.interfaces.nsIFileInputStream);
    var cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
                            .createInstance(Components.interfaces.nsIConverterInputStream);
    fstream.init(this.fptr, -1, 0, 0);
    cstream.init(fstream, null, 0, 0);
    var data = "";
    // let (str = {}) { // use this only when using javascript 1.8
    var str = {};
      cstream.readString(0xffffffff, str);
      data = str.value;
    // }
    cstream.close();
    return data;
  }
};

【讨论】:

  • File() 采取什么论据?
  • @NullUserException 文件的路径,相对于当前文件。喜欢var file = new File('xxx.csv');
  • 嗯 Firefox 在这条线上窒息:let (str = {}) {
  • @NullUserException 我已经更新了我的解决方案。它在 Javascript 1.8 规范中。只需使用我最新的代码。
  • 该死,现在 FF 告诉我 Error: A script from "file://" was denied UniversalXPConnect privileges. 我稍后会尝试修改设置,但从我在这里阅读的内容来看,它看起来不太好:bugzilla.mozilla.org/show_bug.cgi?id=790913
【解决方案2】:

这是一个在本地或服务器上工作的外部文件中使用 JSON 数据的示例。此示例仅使用浏览器的语言设置来加载具有本地化 html 的

<html><meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<head>
<script>
    function setLang(){
        for (var i=0;i<items.length;i++){
            term=document.getElementById(items[i].id)
            if (term) term.innerHTML=items[i].value
        }
    }
    var lang=navigator.userLanguage || navigator.language;
    var script=document.createElement("script");
    script.src=document.URL+"-"+lang.substring(0,2)+".js"
    var head = document.getElementsByTagName('head')[0]
    head.insertBefore(script,head.firstChild)
</script>
</head>
<body onload='setLang()'>
<div id="string1" class="txt">This is the default text of string1.</div> 
<div id="string2" class="txt">This is the default text of string2.</div>
</body></html>

这个数据文件看起来像:

items=[
{"id":"string1","value":"Localized text of string1."},
{"id":"string2", "value":"Localized text of string2."}
];

但是您可以使用任何参数来有条件地加载适当的文件(它将作为

中的第一个标签插入,因此它可以在任何地方使用)并且JSON格式能够处理大量数据.您可能希望将函数 setLang 重命名为更合适的名称并对其进行修改以满足您的需求,例如...对于每个我添加一行,然后添加带有数据的字段(看起来您已经掌握了该部分的句柄)你的 JSON 看起来像:
items=[
{"fname":"john","lname":"smith","address":"1 1st St","phone":"555-1212"},
{"fname":"jane","lname":"smith","address":"1 1st St","phone":"555-1212"}
];

如果您需要预处理您的数据,awk 非常方便 - 它类似于:(未经测试的猜测)

awk 'BEGIN{FS=",";print "items=[\n"}
{printf "{\"fname\":\"%s\",\"lname\":\"smith\",\"address\":\"1 1st St\",\"phone\":\"555-1212\"},\n", $1, $2, $3, $4}
END{print "];"}' file.csv > file.js

编辑:现在 OP 更清晰了,只有 mozilla 浏览器允许开箱即用的 file:// 上的 XMLHttpRequest 和 chrome(可能是其他基于 webkit 的浏览器)可以配置为允许它。知道它可能在 IE

var filePath = "your_file.txt";
xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET",filePath,false);
xmlhttp.overrideMimeType('text/plain');
xmlhttp.send(null);
//maybe check status !=404 here
var fileContent = xmlhttp.responseText;
var fileArray = fileContent.split('\n')
var n = fileArray.length;
//process your data from here probably using split again for ','

我将最初的 json-p 变体留给可能有类似问题但对其数据格式有一定控制权的其他人,因为它适用于所有支持 javascript 的浏览器。但是,如果有人知道一种使它适用于 IE 的方法(除了运行小型 Web 服务器),请编辑。

编辑 2:

在 mozilla 浏览器中,您还可以使用 iframe

<html>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<head>
<script>
function showContents(frameObject){
    alert(frameObject.contentDocument.body.innerHTML);
    //replace with your code
}
</script>
</head>
<body onload='showContents()'>
<iframe id="frametest" src="data.txt" onload="showContents(this);" 
    style="visibility:hidden;display:none"></iframe>
</body></html>

【讨论】:

  • 很抱歉,我无法控制文件的格式,也无法在使用 JS 加载文件之前以任何方式预处理文件。否则我不会问这个问题,因为这是最简单的方法。它必须能够处理任意数据
  • 其实这不是JSON文件,而是JSONP脚本。
  • 是的,我只是说它是一个包含 json 数据的文件,因为我不想暗示它需要一个 json-p 样式的回调函数包装器。它基本上充当一个包含文件,可以包含变量、函数等......但由于 OP 更新格式固定为 csv,XMLHttpRequest 确实是唯一的方法,并且不可移植,除非有是获取“外部”脚本的 innerHTML 的一些技巧... iframe 方法默认情况下也只能在 mozilla 浏览器中使用
  • +1 这是我尝试的第一件事,但由于 Chrome 的过度保护安全策略而失败了。在 FF 上开箱即用。如果你使用--allow-file-access-from-files,它可以在 Chrome 上运行,它也可以在 IE10 上运行(有一个弹出窗口询问你是否要允许被阻止的内容)。与FileReader 不同,当您处理二进制文件时,Ajax 可能会有些混乱,但我认为我可以忍受。这似乎是迄今为止最好的解决方案。
  • @NullUserException “处理二进制文件时,Ajax 可能会有点混乱”。这是归一化造成的。如果您期望二进制响应,请使用 XHR 实例的适当 responseType 属性,例如"arraybuffer""blob".
【解决方案3】:

假设 csv 文件与应用程序位于同一目录中,我将使用 AJAX 加载文件。据我所知,可以获取文本格式的文件,然后对其进行解析。这应该适用于 IE 和 Firefox,但不适用于 Chrome(除非使用 --allow-file-access-from-files 命令行设置运行 chrome)。

【讨论】:

    【解决方案4】:

    这可以很容易地使用 javascript XMLHttpRequest() 类来完成:

    function FileHelper()
    {}
    {
        FileHelper.readStringFromFileAtPath = function(pathOfFileToReadFrom)
        {
            var request = new XMLHttpRequest();
            request.open("GET", pathOfFileToReadFrom, false);
            request.send(null);
            var returnValue = request.responseText;
    
            return returnValue;
        }
    }
    
    ...
    
    var text = FileHelper.readStringFromFileAtPath ( "mytext.txt" );
    

    【讨论】:

      【解决方案5】:

      据我了解,文件的内容完全在您的控制之下,它不一定是特定的格式? 而你只需要一种阅读方式?

      您可以声明一个全局函数“handleFile”。 在您的外部文件中,内容必须是这样的:

      handleFile('Mark Rodgers,mark.rodgers@company.com,Accounting');
      

      要“读取”文件,只需添加一个带有相应 src 属性的脚本元素。 在你的函数“handleFile”中,你得到你的内容。

      文件的位置可能最初必须由用户设置,但之后您可以将位置保存在 localStorage 或类似的东西中。

      【讨论】:

      • 很遗憾,文件的格式在我的控制之下。
      【解决方案6】:

      确保文件在同一目录或子目录下,使用 AJAX 加载文件。

      与脚本标签不同,您可以访问内容。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-12-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多