【问题标题】:Add sheet to excel output of Jquery datatables将工作表添加到 Jquery 数据表的 excel 输出中
【发布时间】:2021-07-07 23:43:41
【问题描述】:

编辑 - 解决方案:

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

我在函数中添加了一些 cmets 来反映这一点。

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

问题:

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

当前解决方案:

我有一个函数可以让我动态地将新工作表添加到我的 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],
              ]);
            }
          }
        ],

输出/错误:

这里是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 给出和错误说工作簿需要修复。修复完成后,表测试为空白(无数据)并生成以下消息和日志文件:

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

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

【问题讨论】:

  • 虽然我个人不知道,但您可能会觉得 thisthis 很有趣。
  • @StackSlave 感谢您的提示。无法为此使用 CSV,因为输出需要易于我们的最终用户使用。我最终解决了问题,您可以在我对问题的编辑中看到解决方案。

标签: javascript jquery excel xml datatables


【解决方案1】:

您的方法似乎基于this example

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

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

其他一些注意事项:

  • 我将主调用中的参数重新排列为addSheet()
  • 我假设您在每张工作表中都没有“标题”行 - 所以该参数现在是 null
  • 因为您没有从其他表中读取数据,所以没有要处理的列标题。

这是完整页面,您可以将其复制/粘贴到独立网页中并自己运行以进行测试/验证。

标题中可能有一些您不需要的额外按钮相关资源(例如 PDF 处理),因为它取自我拥有的另一个测试模板。

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

最后一点:此代码仅处理一张额外的工作表。但现在它已经奏效了,添加任意数量的内容很简单。

<!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>$320,800</td>
            </tr>
            <tr>
                <td>Garrett Winters</td>
                <td>Accountant</td>
                <td>Tokyo</td>
                <td>63</td>
                <td>2011/07/25</td>
                <td>$170,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>$86,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>

【讨论】:

  • 谢谢!我测试了您的解决方案,它运行良好。我在测试您的解决方案后查看了我的解决方案,并注意到在您的输出中 r:id 的设置不同。当我查看我的代码时,我注意到我正在使用rId2,它已经在使用中。这一更改使我的代码正常工作。我对原始问题进行了编辑以反映此解决方案。
猜你喜欢
  • 1970-01-01
  • 2014-07-09
  • 2017-12-21
  • 2016-08-25
  • 2015-05-27
  • 1970-01-01
  • 1970-01-01
  • 2010-11-07
相关资源
最近更新 更多