web-dev-qa-db-fra.com

Utilisation d'un fichier local comme source de données en JavaScript

Contexte:

Je veux créer une "application" qui utilise JavaScript/HTML seulement et qui peut être ouverte par un navigateur directement depuis le système de fichiers. Cette application doit pouvoir lire les données d'un autre fichier. Je vais ensuite utiliser JS pour l'analyser et rendre les pages. À titre d'exemple simplifié, imaginez que j'ai un fichier CSV (télécharger ici) :

Mark Rodgers,[email protected],Accounting
[...]
Melissa Jones,[email protected],CEO

Je veux pouvoir lire le fichier en utilisant JS et utiliser des données pour générer ma page.

Ce que j'ai accompli jusqu'à présent:

Démo (clic droit -> "Enregistrer sous" pour enregistrer le HTML sur votre ordinateur). Il est également disponible sur jsfiddle de manière semi-brisée (la disposition est brisée, mais elle devrait toujours être fonctionnelle).

Faites simplement glisser et déposez le fichier texte CSV dans la zone de glisser-déposer, ou sélectionnez le fichier texte à l'aide du menu Fichier, et JavaScript lira, analysera le fichier et remplira le tableau.

Cela repose sur l'API FileReader; la majeure partie du levage lourd est effectuée par cette fonction:

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>';
    }
}

C'est presque OK, mais cela incite l'utilisateur à charger manuellement un fichier. Idéalement, il devrait pouvoir le charger automatiquement, mais pour des raisons de sécurité, aucun navigateur ne le permettra ... pour le moment.

Exigences de la solution:

  • Doit travailler hors ligne; c'est-à-dire qu'il ne peut compter sur aucun service en ligne. Cela inclut également les serveurs HTTP exécutés sur la machine locale. L'idée est de le faire fonctionner sur n'importe quel ordinateur avec juste un navigateur installé.

  • Doit fonctionner lorsque la page est ouverte en utilisant le file:/// protocole (ie: une page HTML sur le disque dur).

  • Ne doit pas s'appuyer sur des modules complémentaires tiers (par exemple: Flash, Java, shudders ActiveX). Je suis presque sûr que cela ne fonctionnerait probablement pas de toute façon si la page est en file:///

  • Il doit pouvoir accepter des données arbitraires . Cela exclut le chargement d'un fichier dans un format correct qui est prêt à être consommé comme JSON.

  • Si cela fonctionne sur (idéalement les deux) Firefox ou Chrome ça va. Il est également OK de s'appuyer sur des API expérimentales

Je connais le nom du fichier à l'avance, il pourrait donc être codé dans le code HTML lui-même. N'importe la solution qui me permet de lire un fichier à partir du disque est très bien, il n'a pas besoin d'utiliser l'API FileReader.

Donc, s'il y a un hack intelligent pour charger un fichier dans une page, ça va aussi (peut-être le charger dans un iframe invisible et demander à JS de récupérer le contenu); c'est OK aussi.

30
NullUserException

Voici le code que j'ai utilisé pour Firefox, qui est pas portable, mais travaux:

Comme OP l'a commenté, enablePrivilege() est obsolète, cela devrait être considéré comme utilisable. Mais comme mon Firefox utilisant le profil précédent fonctionne toujours avec mon code, je creuse un peu dans le prefs.js (comme about:config masque ces paramètres,) Et voici les paramètres dont vous avez besoin pour que cela fonctionne.

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", "");

Et voici le code:

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;
  }
};
4
xiaoyi

Voici un exemple qui utilise des données JSON dans un fichier externe qui fonctionne localement ou sur un serveur. Cet exemple utilise simplement le paramètre de langue du navigateur pour charger un <script> avec du code HTML localisé, puis traite son objet json pour réinitialiser les données dans les balises indiquées avec un contenu localisé

<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>

Les fichiers de données pour cela ressemblent à ceci:

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

mais vous pouvez utiliser n'importe quel paramètre pour charger conditionnellement le fichier approprié (il sera inséré comme première balise dans <head>, donc il sera utilisable n'importe où) et le format JSON est capable de gérer une grande variété de données. Vous voudrez peut-être renommer la fonction setLang en quelque chose de plus approprié et la modifier pour répondre à vos besoins tels que ... pour chaque j'ajoute une ligne, puis ajoutez des champs avec les données (il semble que vous ayez déjà une poignée sur cette partie) et votre JSON ressemblerait à:

items=[
{"fname":"john","lname":"smith","address":"1 1st St","phone":"555-1212"},
{"fname":"jane","lname":"smith","address":"1 1st St","phone":"555-1212"}
];

si vous avez besoin de prétraiter vos données, awk est assez pratique - ce serait quelque chose comme: (guestimate non testé)

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

Edit: maintenant que OP est plus clair, seuls les navigateurs mozilla autorisent XMLHttpRequest sur le fichier: // hors de la boîte et chrome (éventuellement d'autres navigateurs basés sur webkit) peut être configuré pour le permettre. Sachant qu'il peut PAS fonctionner sur IE <10, vous pouvez:

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 ','

Je laisse la variante json-p initiale à d'autres qui peuvent avoir un problème similaire, mais ont un certain contrôle sur leur format de données, car cela fonctionnera sur tous les navigateurs compatibles javascript. Cependant, si quelqu'un connaît un moyen de le faire fonctionner pour IE (autre que l'exécution d'un petit serveur Web), veuillez modifier.

Modifier 2:

Avec les navigateurs mozilla, vous pouvez également utiliser des iframes

<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>
4
technosaurus

En supposant que le fichier csv se trouve dans le même répertoire que l'application, je chargerais le fichier avec AJAX. Autant que je sache, on peut obtenir le fichier au format texte, puis l'analyser. Cela devrait fonctionner dans IE et Firefox, mais ne fonctionne pas dans Chrome (sauf si l'on exécute chrome avec le --allow-file-access-from-files paramètre de ligne de commande).

3
Inkbug

Assurez-vous que le fichier se trouve dans le même répertoire ou dans un sous-répertoire, chargez le fichier avec AJAX.

Contrairement à une balise de script, vous aurez accès au contenu.

1
Gerard Sexton

Cela peut être fait assez facilement en utilisant la classe 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" );
1
user3375451

Si je comprends bien, le contenu du fichier est entièrement sous votre contrôle, et il ne doit pas nécessairement être un format spécifique? Et vous avez seulement besoin d'un moyen de lire?

Vous pouvez déclarer une fonction globale "handleFile". Dans votre fichier externe, le contenu devrait être comme ceci:

handleFile('Mark Rodgers,[email protected],Accounting');

Pour "lire" le fichier, il suffit d'ajouter un élément de script avec un attribut src correspondant. Dans votre fonction "handleFile", vous obtenez votre contenu.

L'emplacement du fichier devrait probablement être initialement défini par l'utilisateur, mais après cela, vous pouvez enregistrer l'emplacement dans localStorage ou quelque chose comme ça.

1
dave