将 sheet 添加到 Jquery 数据表的 excel 输出

Add sheet to excel output of Jquery datatables

编辑 - 解决方案:

好的,我的问题的解决方案非常简单。如果您查看我的 workbook.xml.rels 输出,您会注意到 rId2 已被工作簿的样式使用。解决方案非常简单:不要将 rId2 用于任何新的 sheet。在那个简单的更改之后,我的 excel 输出加载得很好。

我已经在我的函数中添加了一些注释来反映这一点。

非常感谢@andrewjames,他的解决方案帮助我找到了这个错误。

问题:

我正在使用 jquery 数据表来呈现报告。当用户将报告输出到 excel 时,我想使用自定义函数 (generate_excel_sheet).

将 N sheets 附加到输出

当前解决方案:

我有一个函数可以让我动态添加新的 sheet 到我的 excel 输出:

/**
 * Builds a new excel sheet and attaches it to the current workbook
 * @param {Object} xlsx - The excel workbook
 * @param {Number} id   - The id to be associated with the new sheet
 * @param {string} name - The name to be associated with the new sheet
 * @param {Array}  data - The data to be inserted into the new sheet
 */ 
let generate_excel_sheet = (xlsx, id, name, data) => {

  // helper function for generating column identifier letters (A, AA, AB, etc)
  function colName(n) {
    var ordA = 'a'.charCodeAt(0);
    var ordZ = 'z'.charCodeAt(0);
    var len = ordZ - ordA + 1;
    var s = "";
    while(n >= 0) {
      s = String.fromCharCode(n % len + ordA) + s;
      n = Math.floor(n / len) - 1;
    }
    return s.toUpperCase();
  }

  // Add sheet to [Content_Types].xml => <Types>
  var source = xlsx['[Content_Types].xml'].getElementsByTagName('Override')[1];
  var clone = source.cloneNode(true);
  clone.setAttribute('PartName',`/xl/worksheets/${name}.xml`);
  xlsx['[Content_Types].xml'].getElementsByTagName('Types')[0].appendChild(clone);
  
  // Add sheet relationship to xl/_rels/workbook.xml.rels => Relationships
  var source = xlsx.xl._rels['workbook.xml.rels'].getElementsByTagName('Relationship')[0];
  var clone = source.cloneNode(true);
  clone.setAttribute('Id',`rId${id}`); // CANNOT USE rId2, see solution
  clone.setAttribute('Target',`worksheets/${name}.xml`);
  xlsx.xl._rels['workbook.xml.rels'].getElementsByTagName('Relationships')[0].appendChild(clone);
  
  // Add new sheet to xl/workbook.xml => <workbook><sheets>
  var source = xlsx.xl['workbook.xml'].getElementsByTagName('sheet')[0];
  var clone = source.cloneNode(true);
  clone.setAttribute('name',name);
  clone.setAttribute('sheetId',`${id}`);
  clone.setAttribute('r:id',`rId${id}`); // CANNOT USE rId2, see solution
  xlsx.xl['workbook.xml'].getElementsByTagName('sheets')[0].appendChild(clone);

  // build out the following from data:
  // * <row> for each row
  // * <c>   for each item in each row 
  var body = '';
  for (i = 0; i < data.length; i++) {
    body += `<row  r="${i+1}">`;
    for (j = 0; j < data[i].length; j++) {
      body += 
        `<c r="${colName(j)}${i+1}" t="inlineStr">` +
          `<is>` +
            `<t>${data[i][j]}</t>` +
          `</is>` +
        `</c>`
    }
    body += `</row>`;
  }

  // build <col> elements for each column in data
  var columns = '';
  for (i = 0; i < data[0].length; i++) {
    columns += `<col customWidth="1" width="14.850000000000001" min="${i+1}" max="${i+1}" />`;
  }

  // build the sheet to be appended to the workbook
  var newSheet = 
    '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'+
    '<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac" mc:Ignorable="x14ac">'+
      '<cols>' +
        columns +
      '</cols>' +
      '<sheetData>' +
        body +
      '</sheetData>' +
    '</worksheet>';

  // Add sheet to xl/worksheets
  xlsx.xl.worksheets[`${name}.xml`] = $.parseXML(newSheet);
}

然后当我构建我的数据表时,我使用以下代码来构建我的 excel 按钮:

        buttons: [
          'copy',
          {
            extend: 'excelHtml5',
            title: rpt_title, 
            messageTop: rpt_message,
            customize: function(xlsx) {
              generate_excel_sheet(xlsx, 2, "test", [
                ["test1", 123],
                ["test2", 456],
                ["test3", 789],
              ]);
            }
          }
        ],

Output/Errors:

这是 xlsx 文件中各种 xml 文件的输出:

[Content_Types].xml

<?xml version="1.0" encoding="UTF-8" standalone="true"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
  <Default ContentType="application/xml" Extension="xml"/>
  <Default ContentType="application/vnd.openxmlformats-package.relationships+xml" Extension="rels"/>
  <Default ContentType="image/jpeg" Extension="jpeg"/>
  <Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml" PartName="/xl/workbook.xml"/>
  <Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml" PartName="/xl/worksheets/sheet1.xml"/>
  <Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml" PartName="/xl/styles.xml"/>
  <Override ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml" PartName="/xl/worksheets/test.xml"/>
</Types>

workbook.xml.rels

<?xml version="1.0" encoding="UTF-8" standalone="true"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
  <Relationship Target="worksheets/sheet1.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Id="rId1"/>
  <Relationship Target="styles.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Id="rId2"/>
  <Relationship Target="worksheets/test.xml" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Id="rId2"/>
</Relationships>

workbook.xml

<?xml version="1.0" encoding="UTF-8" standalone="true"?>
<workbook xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
  <fileVersion rupBuild="24816" lowestEdited="5" lastEdited="5" appName="xl"/>
  <workbookPr autoCompressPictures="0" showInkAnnotation="0"/>
  <bookViews>
    <workbookView tabRatio="500" windowHeight="19020" windowWidth="25600" yWindow="0" xWindow="0"/>
  </bookViews>
  <sheets>
    <sheet r:id="rId1" sheetId="1" name="Sheet1"/>
    <sheet r:id="rId2" sheetId="2" name="test"/>
  </sheets>
  <definedNames/>
</workbook>

test.xml

<?xml version="1.0"?>
<worksheet mc:Ignorable="x14ac" xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
  <cols>
    <col max="1" min="1" width="14.850000000000001" customWidth="1"/>
    <col max="2" min="2" width="14.850000000000001" customWidth="1"/>
  </cols>
  <sheetData>
    <row r="1">
      <c r="A1" t="inlineStr">
        <is>
          <t>test1</t>
        </is>
      </c>
      <c r="B1" t="inlineStr">
        <is>
          <t>123</t>
        </is>
      </c>
    </row>
    <row r="2">
      <c r="A2" t="inlineStr">
        <is>
          <t>test2</t>
        </is>
      </c>
      <c r="B2" t="inlineStr">
        <is>
          <t>456</t>
        </is>
      </c>
    </row>
    <row r="3">
      <c r="A3" t="inlineStr">
        <is>
          <t>test3</t>
        </is>
      </c>
      <c r="B3" t="inlineStr">
        <is>
          <t>789</t>
        </is>
      </c>
    </row>
  </sheetData>
</worksheet>

从这里 excel 给出错误提示,需要修复工作簿。修复完成后 sheet 测试为空白(无数据)并生成以下消息和日志文件:

"Excel 已完成文件级验证和修复。此工作簿的某些部分可能已被修复或丢弃。 删除的记录:来自 /xl/workbook.xml 部分(工作簿)sheet 的工作sheet 属性

<?xml version="1.0" encoding="UTF-8" standalone="true"?>
<recoveryLog xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
  <logFileName>error087400_01.xml</logFileName>
  <summary>Errors were detected in file 'C:\Users\cmaxie\Downloads\Transaction Detail Drill Report (25).xlsx'</summary>
  <additionalInfo>
    <info>Excel completed file level validation and repair. Some parts of this workbook may have been repaired or discarded.</info>
  </additionalInfo>
  <removedRecords>
  <removedRecord>Removed Records: Worksheet properties from /xl/workbook.xml part (Workbook)</removedRecord>
  </removedRecords>
</recoveryLog>

看起来您的方法可能基于 this example

我采用了该代码并对其进行了最少的更改,以处理您的测试数据。因此,虽然原始演示处理来自多个不同数据表的数据(并将每个 table 加载到它自己的 sheet 中),但现在我的方法从一个简单的数组数组中获取“额外”数据,例如:

var data = [ ["test1", 123], ["test2", 456], ["test3", 789] ];

一些其他注意事项:

  • 我re-arranged主要调用的参数是addSheet().
  • 我假设您在每个 sheet 中都没有“标题”行 - 所以该参数现在是 null
  • 因为您没有从其他 table 读取数据,所以没有要处理的列标题。

这是完整的页面,您可以将其 copy/paste 放入 stand-alone 网页中,然后 运行 自己使用 test/verify。

它可能在 header 中有一些您不需要的额外 Buttons-related 资源(例如 PDF 处理),因为它取自我拥有的另一个测试模板。

你显然可以删除这些 - 并删除任何你不再需要的额外自定义 Excel 代码(但我留在原地,或者只是注释掉)。

最后一点:此代码只处理一个额外的 sheet。但现在已经可以使用了,您可以根据需要轻松添加。

<!doctype html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Demo</title>

  <script type="text/javascript" src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
  <script type="text/javascript" src="https://cdn.datatables.net/1.10.23/js/jquery.dataTables.min.js"></script>
  <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.23/css/jquery.dataTables.min.css"/>

  <link rel="stylesheet" type="text/css" href="https://datatables.net/media/css/site-examples.css">

  <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/buttons/1.6.5/css/buttons.dataTables.min.css"/> 
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jszip/2.5.0/jszip.min.js"></script>
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.36/pdfmake.min.js"></script>
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.36/vfs_fonts.js"></script>
  <script type="text/javascript" src="https://cdn.datatables.net/buttons/1.6.5/js/dataTables.buttons.min.js"></script>
  <script type="text/javascript" src="https://cdn.datatables.net/buttons/1.6.5/js/buttons.colVis.min.js"></script>
  <script type="text/javascript" src="https://cdn.datatables.net/buttons/1.6.5/js/buttons.flash.min.js"></script>
  <script type="text/javascript" src="https://cdn.datatables.net/buttons/1.6.5/js/buttons.html5.min.js"></script>
  <script type="text/javascript" src="https://cdn.datatables.net/buttons/1.6.5/js/buttons.print.min.js"></script>

</head>

<body>

<div style="margin: 20px;">

    <table id="example" class="display dataTable cell-border" style="width:100%">
        <thead>
            <tr>
                <th>Name</th>
                <th>Position</th>
                <th>Office</th>
                <th>Age</th>
                <th>Start date</th>
                <th>Salary</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>Tiger Nixon</td>
                <td>System Architect</td>
                <td>Edinburgh</td>
                <td>61</td>
                <td>2011/04/25</td>
                <td>0,800</td>
            </tr>
            <tr>
                <td>Garrett Winters</td>
                <td>Accountant</td>
                <td>Tokyo</td>
                <td>63</td>
                <td>2011/07/25</td>
                <td>0,750</td>
            </tr>
            <tr>
                <td>Ashton Cox</td>
                <td>Junior Technical Author</td>
                <td>San Francisco</td>
                <td>66</td>
                <td>2009/01/12</td>
                <td>,000</td>
            </tr>
        </tbody>
    </table>

</div>

<script>

$(document).ready(function() {

  function buildCols(data) {
    // Builds cols XML.
    //To do: deifne widths for each column.
    //Params:
    //  data: row data.
    //Returns:
    //  String of XML formatted column widths.
    
    var cols = '<cols>';
    
    for (i=0; i<data.length; i++) {
      colNum = i + 1;
      cols += '<col min="' + colNum + '" max="' + colNum + '" width="20" customWidth="1"/>';
    }
    
    cols += '</cols>';
    
    return cols;
  }
  
  function buildRow(data, rowNum, styleNum) {
    // Builds row XML.
    //Params:
    //  data: Row data.
    //  rowNum: Excel row number.
    //  styleNum: style number or empty string for no style.
    //Returns:
    //  String of XML formatted row.
    
    var style = styleNum ? ' s="' + styleNum + '"' : '';
    
    var row = '<row r="' + rowNum + '">';

    for (i=0; i<data.length; i++) {
      colNum = (i + 10).toString(36).toUpperCase();  // Convert to alpha
      
      var cr = colNum + rowNum;
      
      row += '<c t="inlineStr" r="' + cr + '"' + style + '>' +
              '<is>' +
                '<t>' + data[i] + '</t>' +
              '</is>' +
            '</c>';
    }
      
    row += '</row>';
        
    return row;
  }
  
  function getTableData(data, title) {
    // Processes Datatable row data to build sheet.
    //Params:
    //  data: data for new sheet.
    //  title: Title displayed at top of SS or empty str for no title.
    //Returns:
    //  String of XML formatted worksheet.
    
    //var header = getHeaderNames(table);
    //var table = $(table).DataTable();
    var rowNum = 1;
    var mergeCells = '';
    var ws = '';
    
    //ws += buildCols(header);
    ws += '<sheetData>';
    
    //if (title.length > 0) {
    //  ws += buildRow([title], rowNum, 51);
    //  rowNum++;
    //  
    //  mergeCol = ((header.length - 1) + 10).toString(36).toUpperCase();
    //  
    //  mergeCells = '<mergeCells count="1">'+
    //    '<mergeCell ref="A1:' + mergeCol + '1"/>' +
    //               '</mergeCells>';
    //}
                      
    //ws += buildRow(header, rowNum, 2);
    //rowNum++;
    
    // Loop through each row to append to sheet.    
    //table.rows().every( function ( rowIdx, tableLoop, rowLoop ) {
    data.forEach(function (item, index) {
      var rowData = item;
      
      // If data is object based then it needs to be converted 
      // to an array before sending to buildRow()
      ws += buildRow(rowData, rowNum, '');
      
      rowNum++;
    } );
    
    ws += '</sheetData>' + mergeCells;
        
    return ws;

  }
  
  function setSheetName(xlsx, name) {
    // Changes tab title for sheet.
    //Params:
    //  xlsx: xlxs worksheet object.
    //  name: name for sheet.
    
    if (name.length > 0) {
      var source = xlsx.xl['workbook.xml'].getElementsByTagName('sheet')[0];
      source.setAttribute('name', name);
    }
  }
  
  function addSheet(xlsx, data, title, name, sheetId) {
    //Clones sheet from Sheet1 to build new sheet.
    //Params:
    //  xlsx: xlsx object.
    //  data: data for new shet.
    //  title: Title for top row or blank if no title.
    //  name: Name of new sheet.
    //  sheetId: string containing sheetId for new sheet.
    //Returns:
    //  Updated sheet object.
    
    //Add sheet2 to [Content_Types].xml => <Types>
    //============================================
    var source = xlsx['[Content_Types].xml'].getElementsByTagName('Override')[1];
    var clone = source.cloneNode(true);
    clone.setAttribute('PartName','/xl/worksheets/sheet' + sheetId + '.xml');
    xlsx['[Content_Types].xml'].getElementsByTagName('Types')[0].appendChild(clone);
    
    //Add sheet relationship to xl/_rels/workbook.xml.rels => Relationships
    //=====================================================================
    var source = xlsx.xl._rels['workbook.xml.rels'].getElementsByTagName('Relationship')[0];
    var clone = source.cloneNode(true);
    clone.setAttribute('Id','rId'+sheetId+1);
    clone.setAttribute('Target','worksheets/sheet' + sheetId + '.xml');
    xlsx.xl._rels['workbook.xml.rels'].getElementsByTagName('Relationships')[0].appendChild(clone);
    
    //Add second sheet to xl/workbook.xml => <workbook><sheets>
    //=========================================================
    var source = xlsx.xl['workbook.xml'].getElementsByTagName('sheet')[0];
    var clone = source.cloneNode(true);
    clone.setAttribute('name', name);
    clone.setAttribute('sheetId', sheetId);
    clone.setAttribute('r:id','rId'+sheetId+1);
    xlsx.xl['workbook.xml'].getElementsByTagName('sheets')[0].appendChild(clone);
    
    //Add sheet2.xml to xl/worksheets
    //===============================
    var newSheet = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'+
      '<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac" mc:Ignorable="x14ac">'+
      getTableData(data, title) +
      
      '</worksheet>';

    xlsx.xl.worksheets['sheet' + sheetId + '.xml'] = $.parseXML(newSheet);
    
  }


  var table = $('#example').DataTable( {
    dom: 'Brftip',
    buttons: [
      {
        extend: 'excelHtml5',
        text: 'Excel',
        customize: function( xlsx ) {
          //setSheetName(xlsx, 'Calls');
          
          // Add more of these 2 lines, to add more sheets, as needed:
          var data = [ ["test1", 123], ["test2", 456], ["test3", 789] ];
          addSheet(xlsx, data, null, 'TabName2', '2');
        }
          
      }
    ]
  } );
  
} );

</script>

</body>
</html>