2015年9月24日星期四

Web端导出CSV - 木的树

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
Web端导出CSV - 木的树  阅读原文»

  前端导出文件大部分还是通过服务器端的方式生成文件,然后传递到客户端。但很多情况下当我们导出CSV时并不需要后端参与,甚至没有后端。

  

  做过WebGIS的同学经常会碰到这种场景,用户的兴趣点数据以csv文件形式上传到web应用中以表格形式展示,并可以编辑属性信息,编辑完成后需要将数据下载到本地。特别是对一些敏感数据,用户不希望传递到应用服务器端,整个过程完全在客户端进行。

  上传过程我们暂且不讨论,只讨论生成CSV以及下载过程。

  

CSV的生成

  问题一:如何分行分列?

  思路:分行使用“\n”,分列使用","

var str = "col1,col2,col3\nvalue1,value2,value3";

  实际应用中发现导出的csv用excel打开后,列可以分开但行无法分开。

  解决方法是,将生成的csv字符串使用encodeURIComponent编码

str = encodeURIComponent(str);

  问题二:字段值中含有特殊符号影响csv文件的正确解读,如:“,”,"\n"

  思路:将含有特殊符号的字段用双引号包装起来,如:a,b => "a,b"

var textField = '"';
if (value && /[,\r\n]/g.test(value)) {
value
= textField + value + textField;
}

  实际应用发现少考虑了一种情况,如果字段值中含有‘ " ’这个符号,经过上方代码处理反而会出现问题:a"b => "a"b"。显然是语法错误。

  解决方法是将"换成"",a"b => "a""b"

var textField = '"';
if (value && /[",\r\n]/g.test(value)) {
value = textField + value.replace(/(")/g, '""') + textField;
}

  在解决以上问题后生成CSV字符串代码如下

//data: 数据数组,每个元素都包含_outFields中指定的字段名
//
_outFields: 字段名称数组
exports.createCSVStr = function(data, _outFields) {
var textField = '"';
var content = "";
var len = 0,
n
= 0,
comma
= "",
value
= "";
try {
array.forEach(_outFields,
function(_field) {
content
= content + comma + _field;
comma
= ",";
});

content
= content + "\r\n";
len
= data.length;
n
= _outFields.length;
for (var i = 0; i < len; i++) {
comma
= "";
for (var m = 0; m < n; m++) {
var _field = _outFields[m];
value
= data[_field];
if (!value && typeof value !== "number") {
value
= "";
}
if (value && /[",\r\n]/g.test(value)) {
value
= textField + value.replace(/(")/g, '""') + textField;
}
content
= content + comma + value;
comma
= ",";
}
content
= content + "\r\n";
}
}
catch (err) {
console.error(err);
content
= "";
}

return content;
};

  问题三:如果字段中含有希伯来文、法语、德语等文字('éà; ça; 12\nà@€; çï; 13'),导出的csv文件在Excel中打开后,这些文字呈现出乱码

  解决方法:严格来说这并不是csv文件的问题,而是Excel处理文件编码方式问题,Excel默认并不是以UTF-8来打开文件,所以在csv开头加入BOM,告诉Excel文件使用utf-8的编码方式。

var BOM = "\uFEFF";
var csvStr = BOM + csvStr;

  实际应用中发现,这种处理方式在windows中的Excel中打开后可以正常显示,但在mac上的Excel无法正确显示。目前没有完全的解决方案,但mac中可以使用自带的Numbers软件打开,不会出现乱码问题。

  

CSV的下载方式

  问题一:如何在解决不同浏览器中的下载问题?

  思路:

  • IE10以下,利用execCommand方法来保存csv文件
    var oWin = window.top.open("about:blank", "_blank");
    oWin.document.write(encodeURIComponent(text));
    oWin.document.close();
    oWin.document.execCommand(
    'SaveAs', true, filename);
    oWin.close();

    在实际应用中浏览器会打开一个新窗口,并弹出保存文件对话框,而对话框中保存类型时,只有html和text两项可选,此时需要在文件名中手动加上“.csv”后缀

  • IE10以及Edge浏览器使用navigator.msSaveBlob(blob);虽然这些浏览器也支持上面的方法,但可以避免上面遇到的问题。
    var BOM = "\uFEFF";
    var csvData = new Blob(, { type: 'text/csv' });
    navigator.msSaveBlob(csvData, filename);

    msSaveBlob是IE的私有方法,只有IE10及以上和Edge浏览器支持。

  • Firefox、Chrome、Safari浏览器中使用a标签,利用html5中增加的download属性来下载csv
    var link = html.create("a", {
    href: 'data:attachment/csv;charset=utf-8,' + BOM + encodeURIComponent(text)
    ,
    target:
    '_blank',
    download: filename
    },
    this.domNode);
    if (has('safari')) {
    // # First create an event
    var click_ev = document.createEvent("MouseEvents");
    // # initialize the event
    click_ev.initEvent("click", true /* bubble */ , true /* cancelable */ );
    // # trigger the evevnt/
    link.dispatchEvent(click_ev);
    }
    elseDOM特性节点ATTRIBUTE - 小火柴的蓝色理想  阅读原文»

    定义

      每个元素都有一个或多个特性,这些特性的用途是给出相应元素或内容的附加信息。实质上,特性节点就是存在于元素的attributes属性中的节点。

    特征  

      nodeType:2
      nodeName:特性的名称
      nodeValue:特性的值
      parentNode:null
      childNode:chrome、firefox下为undefined,safari下为Text,IE9+下为子元素的特性名,IE8-下报错
        [注意]尽管Attribute也是节点,但却不被认为是DOM文档树的一部分,开发人员常用getAttribute()、setAttribute()、removeAttribute(),很少直接引用特性节点

    <div id="box"></div>
    <script>
    var oBox = document.getElementById('box');
    var oAttr = oBox.attributes;
    //(chrome\safari\IE9+\firefox) 2 id box null
    //
    (IE7-) 2 onmsanimationiteration null null
    console.log(oAttr[0].nodeType,oAttr[0].nodeName,oAttr[0].value,oAttr[0].parentNode)
    //(chrome\firefox) undefined
    //
    (safari) Text
    //
    (IE9+) box
    //
    (IE8-) 报错
    console.log(oAttr[0].childNodes[0])
    </script>

    特性节点属性

      Attr对象有3个属性:name、value和specified
        【1】name是特性名称(与nodeName的值相同)
        【2】value是特性的值(与nodeValue的值相同)
        【3】specified是一个布尔值,用以区别特性是在代码中指定的,还是默认的。这个属性的值如果为true,则意味着要么是在HTML中指定了相应特性,要么是通过setAttribute()方法设置了该属性。在IE中,所有未设置过的特性的该属性值都为false,而在其他浏览器中根本不会为这类特性生成对应的特性节点

    <div class="box" id="box"></div>
    <script>
    var oBox = document.getElementById('box');
    var oAttr = oBox.attributes;
    //(chrome\safari\IE8+)class class true
    //
    (firefox)id id true
    //
    (IE7-)onmsanimationiteration onmsanimationiteration true
    console.log(oAttr[0].name,oAttr[0].nodeName,oAttr[0].name == oAttr[0].nodeName)
    //IE7- "null" null false
    //
    其他浏览器 box box true
    console.log(oAttr[0].value,oAttr[0].nodeValue,oAttr[0].value == oAttr[0].nodeValue)
    //IE7- false
    //
    其他浏览器 true
    console.log(oAttr[0].specified)//true
    </script>
    <div class="box" id="box" name="abc" index="123" title="test"></div>
    <script>
    var oBox = document.getElementById('box');
    console.log(oBox.attributes.id.specified)
    //true
    console.log(oBox.attributes.onclick.specified)//在IE7-浏览器下会返回false,在其他浏览器下会报错
    </script>

    特性属性attributes

      Element类型是使用attributes属性的唯一一个DOM节点类型。attributes属性中包含一个NamedNodeMap,与NodeList类似,也是一个动态的集合。元素的每一个特性都由一个Attr节点表示,每个节点都保存在NamedNodeMap对象中。

      【attributes属性的四个方法】

        [a]getNamedItem(name):返回nodeName属性等于name的节点
        removeNamedItem(name):从列表中移除nodeName属性等于name的节点
        [c]setNamedItem(node):向列表中添加节点,以节点的nodeName属性为索引
        [d]item(pos):返回位于数字pos位置处的节点,也可以用方括号法[]简写

    <div class="box" id="box" name="abc" index="123" title="test"></div>
    <script>
    var oBox = document.getElementById('box');
    console.log(oBox.attributes);
    //NamedNodeMap {0: class, 1: id, 2: name, 3: index, 4: title}
    var getTest = oBox.attributes.getNamedItem("index");
    console.log(getTest);
    //index = "123"
    var removeTest = oBox.attributes.removeNamedItem("class");
    console.log(removeTest);
    //class = "box"
    console.log(oBox.attributes.getNamedItem("class"));//null
    console.log(oBox.attributes.setNamedItem(removeTest));//null
    console.log(oBox.attributes.setNamedItem(getTest));//index = "123"
    console.log(oBox.attributes.item(0));//id="box"(每个浏览器获取的不一样)
    </script>

      attributes属性中包含一系列节点,每个节点的nodeName就是特性的名称,节点的nodeValue就是特性的值

    <div class="box" id="box" name="abc" index="123" title="test"></div>
    <script>
    var oBox = document.getElementById('box');
    console.log(oBox.attributes);
    //NamedNodeMap {0: class, 1: id, 2: name, 3: index, 4: title}
    console.log(oBox.attributes.id.nodeName);//"id"
    console.log(oBox.attributes.id.nodeValue);//"box"
    </script>

      【特性遍历】

        attributes属性主要用于特性遍历。在需要将DOM结构序列化为XML或HTML字符串时,多数都会涉及遍历元素特性

    function outputAttributes(element){
    var pairs = new Array(),attrName,attrValue,i,len;
    for(i = 0,len=element.attributes.length;i<len;i++){
    attrName
    = element.attributes.nodeName;
    attrValue
    = element.attributes.nodeValue;
    pairs.push(attrName
    +"=\"" + attrValue + "\"");
    }
    return pairs.join(" ");
    }

        

        [注意1]针对attributes对象中的特性,不同浏览器返回的顺序不同

    <div class="box" id="box" name="abc" index="123" title="test"></div>
    <script>
    function outputAttributes(element){
    var pairs = new Array(),attrName,attrValue,i,len;
    for(i = 0,len=element.attributes.length;i<len;i++){
    attrName
    = element.attributes.nodeName;
    attrValue
    = element.attributes.nodeValue;
    pairs.push(attrName
    +"=\"" + attrValue + "\"");
    }
    return pairs.join(" ");
    }
    //(chrome\safari)class="box" id="box" name="abc" index="123" title="test"
    //
    (firefox)title="test" index="123" name="abc" id="box" class="box"
    //
    (IE8+)title="test" class="box" id="box" index="123" name="abc"阅读更多内容

没有评论:

发表评论