소스 검색

basic functionality given.

Tomislav Cvetic 7 년 전
부모
커밋
552b61ab2d
7개의 변경된 파일859개의 추가작업 그리고 31개의 파일을 삭제
  1. 2 0
      package.json
  2. 101 26
      src/App.js
  3. 641 0
      src/example_excel_write.js
  4. 56 0
      src/excel/excel.js
  5. 2 2
      src/match/match.js
  6. 1 1
      src/player/player.js
  7. 56 2
      yarn.lock

+ 2 - 0
package.json

@@ -3,7 +3,9 @@
   "version": "0.1.0",
   "private": true,
   "dependencies": {
+    "blob": "^0.0.4",
     "file-saver": "^1.3.3",
+    "msexcel-builder": "^0.0.2",
     "ods": "^1.1.7",
     "react": "^15.5.4",
     "react-dom": "^15.5.4",

+ 101 - 26
src/App.js

@@ -1,11 +1,10 @@
 import React from 'react'
-import XLSX from 'xlsx-style'
+import XLSX from 'xlsx'
 import PlayerList from './player/player-list'
 import MatchList from './match/match-list'
 import Stats from './stats/stats'
-import FileSaver from 'file-saver'
-import Workbook from 'xlsx-workbook'
-
+import { SheetFromArray, Workbook, saveAs } from './excel/excel'
+ 
 class App extends React.Component {
   constructor () {
     super()
@@ -28,7 +27,8 @@ class App extends React.Component {
     this.generatePlayerList = this.generatePlayerList.bind(this)
     this.generateCalendar = this.generateCalendar.bind(this)
     this.filterData = this.filterData.bind(this)
-    this.generateExcel = this.generateExcel.bind(this)
+    this.generateSchedule = this.generateSchedule.bind(this)
+    this.generatePayTable = this.generatePayTable.bind(this)
   }
 
   componentDidMount () {
@@ -129,38 +129,112 @@ class App extends React.Component {
     const filters = {
       date: this.date.value
     }
+    this.generateStats()
     this.setState({ filters })
   }
 
-  generateExcel (event) {
+  generateSchedule (event) {
     event.preventDefault()
 
-    const wopts = {bookType: 'xlsx', bookSST: false, type: 'binary'}
-
-    const matchlist = new Workbook.Workbook()
+    const matchlist = new Workbook()
     matchlist.SheetNames = []
     matchlist.Sheets = {}
-    console.log(matchlist)
-    const worksheets = {}
 
+    const worksheets = {}
     const placeArray = this.state.stats.matchPlaces.concat(['Alle'])
+
     placeArray.forEach((place) => {
-      worksheets[place] = matchlist.add(place)
+      let filter = this.state.filters.date
+      let header = [
+        [`Spielplan für den ${filter} (${place})`],
+        [],
+        ['Ort', 'Zeit', 'Kategorie', 'Spieler 1', '', 'Spieler 2', '', '1. Satz', '2. Satz', '3. Satz', 'WO Grund']
+      ]
+      let matchListPerPlace = this.state.matches.filter((match) => {
+        return (match[1] === filter | filter === 'Alle') & (match[0] === place | place === 'Alle')
+      }).map((match) => 
+        [match[0], match[2], match[3], match[4], match[5], match[6], match[7]]
+      )
+      console.log(matchListPerPlace)
+      worksheets[place] = SheetFromArray(header.concat(matchListPerPlace))
       matchlist.SheetNames.push(place)
       matchlist.Sheets[place] = worksheets[place]
     })
-    console.log(placeArray, worksheets, matchlist)
-
-    const wbout = XLSX.write(matchlist, wopts)
-    function s2ab (s) {
-      const buf = new ArrayBuffer(s.length)
-      const view = new Uint8Array(buf)
-      for (let i = 0; i !== s.length; ++i) {
-        view[i] = s.charCodeAt(i) && 0xFF
+    console.log(matchlist)
+
+    saveAs(matchlist, 'Spielerliste.xlsx')
+  }
+
+  generatePayTable (event) {
+    event.preventDefault()
+
+    const paylist = new Workbook()
+    paylist.SheetNames = []
+    paylist.Sheets = {}
+
+    const worksheets = {}
+    const placeArray = this.state.stats.matchPlaces.concat(['Alle'])
+
+    const payerList = {}
+    this.state.matches.forEach((match) => {
+      if (!!match[4] & !payerList.hasOwnProperty(`${match[3]} ${match[4]}`)) {
+        let foundPlayer = this.state.players.find((player) => {
+          if (!!player[24]) {
+            return (`${player[5]} ${player[6]} / ${player[24]} ${player[25]}` === match[4]) & (match[3] === player[0].replace(/\s+/g, ' '))
+          } else {
+            return (`${player[5]} ${player[6]}` === match[4]) & (match[3] === player[0].replace(/\s+/g, ' '))
+          }
+        })
+        if (!foundPlayer) {
+          console.log(foundPlayer, match)
+          throw Error('Player 4 not found. This is an error!')
+        }
+        payerList[`${match[3]} ${match[4]}`] = [match[0], match[1], match[2], foundPlayer[31]]
       }
-      return buf
-    }
-    FileSaver.saveAs(new Blob([s2ab(wbout)], {type: ''}), 'Spielliste.xlsx')
+      if (!!match[6] & !payerList.hasOwnProperty(`${match[3]} ${match[6]}`)) {
+        let foundPlayer = this.state.players.find((player) => {
+          if (!!player[24]) {
+            return (`${player[5]} ${player[6]} / ${player[24]} ${player[25]}` === match[6]) & (match[3] === player[0].replace(/\s+/g, ' '))
+          } else {
+            return (`${player[5]} ${player[6]}` === match[6]) & (match[3] === player[0].replace(/\s+/g, ' '))
+          }
+        })
+        if (!foundPlayer) {
+          console.log(foundPlayer, match)
+          throw Error('Player 6 not found. This is an error!')
+        }
+        payerList[`${match[3]} ${match[6]}`] = [match[0], match[1], match[2], foundPlayer[31]]
+      }
+    })
+    console.log(payerList)
+
+    placeArray.forEach((place) => {
+      let filter = this.state.filters.date
+      let header = [
+        [`Zahlliste für den ${filter} (${place})`],
+        [],
+        ['bereits bez.', 'Kategorie', 'Name', 'Betrag bez.', 'Quittung abgeben']
+      ]
+
+      let payListPerPlace = [] 
+      this.state.matches.filter((match) => {
+        return (match[1] === filter | filter === 'Alle') & (match[0] === place | place === 'Alle')
+      }).forEach((match) => {
+        if (!!match[4]) {
+          payListPerPlace.push(match)
+        }
+        if (!!match[6]) {
+          payListPerPlace.push(match)
+        }
+      })
+      console.log(payListPerPlace)
+      worksheets[place] = SheetFromArray(header.concat(payListPerPlace))
+      paylist.SheetNames.push(place)
+      paylist.Sheets[place] = worksheets[place]
+    })
+    console.log(paylist)
+
+    saveAs(paylist, 'Zahlliste.xlsx')
   }
 
   render () {
@@ -174,11 +248,12 @@ class App extends React.Component {
           <label htmlFor='Date'>Datum</label>
           <select ref={(input) => { this.date = input }} onChange={this.filterData}>
             <option>Alle</option>
-            {this.state.stats.matchDates.map((date) => (
-              <option>{date}</option>
+            {this.state.stats.matchDates.map((date, key) => (
+              <option key={key}>{date}</option>
             ))}
           </select>
-          <button onClick={this.generateExcel}>Excel-Files generieren.</button>
+          <button onClick={this.generateSchedule} disabled={!this.state.matches.length}>Spielliste generieren</button>
+          <button onClick={this.generatePayTable} disabled={(!this.state.matches.length | !this.state.players.length)}>Zahlliste generieren</button>
         </form>
         <Stats stats={this.state.stats} />
         <PlayerList players={this.state.players} />

+ 641 - 0
src/example_excel_write.js

@@ -0,0 +1,641 @@
+var XLSX = require('xlsx');
+var OUTFILE = '/tmp/example-style.xlsx';
+
+function JSDateToExcelDate(inDate) {
+  return 25569.0 + ((inDate.getTime() - (inDate.getTimezoneOffset() * 60 * 1000)) / (1000 * 60 * 60 * 24));
+}
+
+var defaultCellStyle = { font: { name: "Verdana", sz: 11, color: "FF00FF88"}, fill: {fgColor: {rgb: "FFFFAA00"}}};
+
+// test to see if everything on the left equals its counterpart on the right
+// but the right hand object may have other attributes which we might not care about
+function basicallyEquals(left, right) {
+  if (Array.isArray(left) && Array.isArray(right)) {
+    for (var i = 0; i < left.length; i++) {
+      if (!basicallyEquals(left[i], right[i])) {
+        return false;
+      }
+    }
+    return true;
+  }
+  else if (typeof left == 'object' && typeof right == 'object') {
+    for (var key in left) {
+      if (key != 'bgColor') {
+        if (!basicallyEquals(left[key], right[key])) {
+          if (JSON.stringify(left[key]) == "{}" && right[key] == undefined) return true;
+          if (JSON.stringify(right[key]) == "{}" && left[key] == undefined) return true;
+          return false;
+        }
+      }
+    }
+    return true;
+  }
+  else {
+    if (left != right) {
+      return false;
+    }
+    return true;
+  }
+}
+
+
+var workbook, wbout, wbin;
+
+workbook = {
+  "SheetNames": [
+    "Main"
+  ],
+  "Sheets": {
+    "Main": {
+      "!merges": [
+        {
+          "s": {
+            "c": 0,
+            "r": 0
+          },
+          "e": {
+            "c": 2,
+            "r": 0
+          }
+        }
+      ],
+      "A1": {
+        "v": "This is a submerged cell",
+        "s": {
+          "border": {
+            "left": {
+              "style": "thick",
+              "color": {
+                "auto": 1
+              }
+            },
+            "top": {
+              "style": "thick",
+              "color": {
+                "auto": 1
+              }
+            },
+            "bottom": {
+              "style": "thick",
+              "color": {
+                "auto": 1
+              }
+            }
+          }
+        },
+        "t": "s"
+      },
+      "B1": {
+        "v": "Pirate ship",
+        "s": {
+          "border": {
+            "top": {
+              "style": "thick",
+              "color": {
+                "auto": 1
+              }
+            },
+            "bottom": {
+              "style": "thick",
+              "color": {
+                "auto": 1
+              }
+            }
+          }
+        },
+        "t": "s"
+      },
+      "C1": {
+        "v": "Sunken treasure",
+        "s": {
+          "border": {
+            "right": {
+              "style": "thick",
+              "color": {
+                "auto": 1
+              }
+            },
+            "top": {
+              "style": "thick",
+              "color": {
+                "auto": 1
+              }
+            },
+            "bottom": {
+              "style": "thick",
+              "color": {
+                "auto": 1
+              }
+            }
+          }
+        },
+        "t": "s"
+      },
+      "A2": {
+        "v": "Blank",
+        "t": "s"
+      },
+      "B2": {
+        "v": "Red",
+        "s": {
+          "fill": {
+            "fgColor": {
+              "rgb": "FFFF0000"
+            }
+          }
+        },
+        "t": "s"
+      },
+      "C2": {
+        "v": "Green",
+        "s": {
+          "fill": {
+            "fgColor": {
+              "rgb": "FF00FF00"
+            }
+          }
+        },
+        "t": "s"
+      },
+      "D2": {
+        "v": "Blue",
+        "s": {
+          "fill": {
+            "fgColor": {
+              "rgb": "FF0000FF"
+            }
+          }
+        },
+        "t": "s"
+      },
+      "E2": {
+        "v": "Theme 5",
+        "s": {
+          "fill": {
+            "fgColor": {
+              "theme": 5
+            }
+          }
+        },
+        "t": "s"
+      },
+      "F2": {
+        "v": "Theme 5 Tint -0.5",
+        "s": {
+          "fill": {
+            "fgColor": {
+              "theme": 5,
+              "tint": -0.5
+            }
+          }
+        },
+        "t": "s"
+      },
+      "A3": {
+        "v": "Default",
+        "t": "s"
+      },
+      "B3": {
+        "v": "Arial",
+        "s": {
+          "font": {
+            "name": "Arial",
+            "sz": 24,
+            "color": {
+              "theme": "5"
+            }
+          }
+        },
+        "t": "s"
+      },
+      "C3": {
+        "v": "Times New Roman",
+        "s": {
+          "font": {
+            "name": "Times New Roman",
+            bold: true,
+            underline: true,
+            italic: true,
+            strike: true,
+            outline: true,
+            shadow: true,
+            vertAlign: "superscript",
+            "sz": 16,
+            "color": {
+              "rgb": "FF2222FF"
+            }
+          }
+        },
+        "t": "s"
+      },
+      "D3": {
+        "v": "Courier New",
+        "s": {
+          "font": {
+            "name": "Courier New",
+            "sz": 14
+          }
+        },
+        "t": "s"
+      },
+      "A4": {
+        "v": 0.618033989,
+        "t": "n"
+      },
+      "B4": {
+        "v": 0.618033989,
+        "t": "n"
+      },
+      "C4": {
+        "v": 0.618033989,
+        "t": "n"
+      },
+      "D4": {
+        "v": 0.618033989,
+        "t": "n",
+        "s": {
+          "numFmt": "0.00%"
+        }
+      },
+      "E4": {
+        "v": 0.618033989,
+        "t": "n",
+        "s": {
+          "numFmt": "0.00%",
+          "fill": {
+            "fgColor": {
+              "rgb": "FFFFCC00"
+            }
+          }
+        }
+      },
+      "A5": {
+        "v": 0.618033989,
+        "t": "n",
+        "s": {
+          "numFmt": "0%"
+        }
+      },
+      "B5": {
+        "v": 0.618033989,
+        "t": "n",
+        "s": {
+          "numFmt": "0.0%"
+        }
+      },
+      "C5": {
+        "v": 0.618033989,
+        "t": "n",
+        "s": {
+          "numFmt": "0.00%"
+        }
+      },
+      "D5": {
+        "v": 0.618033989,
+        "t": "n",
+        "s": {
+          "numFmt": "0.000%"
+        }
+      },
+      "E5": {
+        "v": 0.618033989,
+        "t": "n",
+        "s": {
+          "numFmt": "0.0000%"
+        }
+      },
+      "F5": {
+        "v": 0,
+        "t": "n",
+        "s": {
+          "numFmt": "0.00%;\\(0.00%\\);\\-;@",
+          "fill": {
+            "fgColor": {
+              "rgb": "FFFFCC00"
+            }
+          }
+        }
+      },
+      "A6": {
+        "v": "Sat Mar 21 2015 23:47:34 GMT-0400 (EDT)",
+        "t": "s"
+      },
+      "B6": {
+        "v": 42084.99137416667,
+        "t": "n"
+      },
+      "C6": {
+        "v": 42084.99137416667,
+        "s": {
+          "numFmt": "d-mmm-yy"
+        },
+        "t": "n"
+      },
+      "A7": {
+        "v": "left",
+        "s": {
+          "alignment": {
+            "horizontal": "left"
+          }
+        },
+        "t": "s"
+      },
+      "B7": {
+        "v": "center",
+        "s": {
+          "alignment": {
+            "horizontal": "center"
+          }
+        },
+        "t": "s"
+      },
+      "C7": {
+        "v": "right",
+        "s": {
+          "alignment": {
+            "horizontal": "right"
+          }
+        },
+        "t": "s"
+      },
+      "A8": {
+        "v": "vertical",
+        "s": {
+          "alignment": {
+            "vertical": "top"
+          }
+        },
+        "t": "s"
+      },
+      "B8": {
+        "v": "vertical",
+        "s": {
+          "alignment": {
+            "vertical": "center"
+          }
+        },
+        "t": "s"
+      },
+      "C8": {
+        "v": "vertical",
+        "s": {
+          "alignment": {
+            "vertical": "bottom"
+          }
+        },
+        "t": "s"
+      },
+      "A9": {
+        "v": "indent",
+        "s": {
+          "alignment": {
+            "indent": "1"
+          }
+        },
+        "t": "s"
+      },
+      "B9": {
+        "v": "indent",
+        "s": {
+          "alignment": {
+            "indent": "2"
+          }
+        },
+        "t": "s"
+      },
+      "C9": {
+        "v": "indent",
+        "s": {
+          "alignment": {
+            "indent": "3"
+          }
+        },
+        "t": "s"
+      },
+      "A10": {
+        "v": "In publishing and graphic design, lorem ipsum is a filler text commonly used to demonstrate the graphic elements of a document or visual presentation. ",
+        "s": {
+          "alignment": {
+            "wrapText": 1,
+            "horizontal": "right",
+            "vertical": "center",
+            "indent": 1
+          }
+        },
+        "t": "s"
+      },
+      "A11": {
+        "v": 41684.35264774306,
+        "s": {
+          "numFmt": "m/d/yy"
+        },
+        "t": "n"
+      },
+      "B11": {
+        "v": 41684.35264774306,
+        "s": {
+          "numFmt": "d-mmm-yy"
+        },
+        "t": "n"
+      },
+      "C11": {
+        "v": 41684.35264774306,
+        "s": {
+          "numFmt": "h:mm:ss AM/PM"
+        },
+        "t": "n"
+      },
+      "D11": {
+        "v": 42084.99137416667,
+        "s": {
+          "numFmt": "m/d/yy"
+        },
+        "t": "n"
+      },
+      "E11": {
+        "v": 42065.02247239584,
+        "s": {
+          "numFmt": "m/d/yy"
+        },
+        "t": "n"
+      },
+      "F11": {
+        "v": 42084.99137416667,
+        "s": {
+          "numFmt": "m/d/yy h:mm:ss AM/PM"
+        },
+        "t": "n"
+      },
+      "A12": {
+        "v": "Apple",
+        "s": {
+          "border": {
+            "top": {
+              "style": "thin"
+            },
+            "left": {
+              "style": "thin"
+            },
+            "right": {
+              "style": "thin"
+            },
+            "bottom": {
+              "style": "thin"
+            }
+          }
+        },
+        "t": "s"
+      },
+      "C12": {
+        "v": "Apple",
+        "s": {
+          "border": {
+            "diagonalUp": 1,
+            "diagonalDown": 1,
+            "top": {
+              "style": "dashed",
+              "color": {
+                "auto": 1
+              }
+            },
+            "right": {
+              "style": "medium",
+              "color": {
+                "theme": "5"
+              }
+            },
+            "bottom": {
+              "style": "hair",
+              "color": {
+                "theme": 5,
+                "tint": "-0.3"
+              }
+            },
+            "left": {
+              "style": "thin",
+              "color": {
+                "rgb": "FFFFAA00"
+              }
+            },
+            "diagonal": {
+              "style": "dotted",
+              "color": {
+                "auto": 1
+              }
+            }
+          }
+        },
+        "t": "s"
+      },
+      "E12": {
+        "v": "Pear",
+        "s": {
+          "border": {
+            "diagonalUp": 1,
+            "diagonalDown": 1,
+            "top": {
+              "style": "dashed",
+              "color": {
+                "auto": 1
+              }
+            },
+            "right": {
+              "style": "dotted",
+              "color": {
+                "theme": "5"
+              }
+            },
+            "bottom": {
+              "style": "mediumDashed",
+              "color": {
+                "theme": 5,
+                "tint": "-0.3"
+              }
+            },
+            "left": {
+              "style": "double",
+              "color": {
+                "rgb": "FFFFAA00"
+              }
+            },
+            "diagonal": {
+              "style": "hair",
+              "color": {
+                "auto": 1
+              }
+            }
+          }
+        },
+        "t": "s"
+      },
+      "A13": {
+        "v": "Up 90",
+        "s": {
+          "alignment": {
+            "textRotation": 90
+          }
+        },
+        "t": "s"
+      },
+      "B13": {
+        "v": "Up 45",
+        "s": {
+          "alignment": {
+            "textRotation": 45
+          }
+        },
+        "t": "s"
+      },
+      "C13": {
+        "v": "Horizontal",
+        "s": {
+          "alignment": {
+            "textRotation": 0
+          }
+        },
+        "t": "s"
+      },
+      "D13": {
+        "v": "Down 45",
+        "s": {
+          "alignment": {
+            "textRotation": 135
+          }
+        },
+        "t": "s"
+      },
+      "E13": {
+        "v": "Down 90",
+        "s": {
+          "alignment": {
+            "textRotation": 180
+          }
+        },
+        "t": "s"
+      },
+      "F13": {
+        "v": "Vertical",
+        "s": {
+          "alignment": {
+            "textRotation": 255
+          }
+        },
+        "t": "s"
+      },
+      "A14": {
+        "v": "Font color test",
+        "s": {
+          "font": {
+            "color": {
+              "rgb": "FFC6EFCE"
+            }
+          }
+        },
+        "t": "s"
+      },
+      "!ref": "A1:F14"
+    }
+  }
+}
+XLSX.writeFile(workbook, OUTFILE, { defaultCellStyle: defaultCellStyle });
+console.log("open " + OUTFILE)

+ 56 - 0
src/excel/excel.js

@@ -0,0 +1,56 @@
+import XLSX from 'xlsx'
+import FileSaver from 'file-saver'
+import Blob from 'blob'
+
+function datenum(v, date1904) {
+  if(date1904) v+=1462;
+  var epoch = Date.parse(v);
+  return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000);
+}
+
+export function SheetFromArray(data, opts) {
+  var ws = {};
+  var range = {s: {c:10000000, r:10000000}, e: {c:0, r:0 }};
+  for(var R = 0; R != data.length; ++R) {
+    for(var C = 0; C != data[R].length; ++C) {
+      if(range.s.r > R) range.s.r = R;
+      if(range.s.c > C) range.s.c = C;
+      if(range.e.r < R) range.e.r = R;
+      if(range.e.c < C) range.e.c = C;
+      var cell = {v: data[R][C] };
+      if(cell.v == null) continue;
+      var cell_ref = XLSX.utils.encode_cell({c:C,r:R});
+      
+      if(typeof cell.v === 'number') cell.t = 'n';
+      else if(typeof cell.v === 'boolean') cell.t = 'b';
+      else if(cell.v instanceof Date) {
+        cell.t = 'n'; cell.z = XLSX.SSF._table[14];
+        cell.v = datenum(cell.v);
+      }
+      else cell.t = 's';
+      
+      ws[cell_ref] = cell;
+    }
+  }
+  if(range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range);
+  return ws;
+}
+ 
+export function Workbook() {
+  if(!(this instanceof Workbook)) return new Workbook();
+  this.SheetNames = [];
+  this.Sheets = {};
+}
+
+function s2ab(s) {
+  var buf = new ArrayBuffer(s.length);
+  var view = new Uint8Array(buf);
+  for (var i=0; i!=s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
+  return buf;
+}
+
+export function saveAs(workbook, filename) {
+  const wopts = {bookType: 'xlsx', bookSST: false, type: 'binary'}
+  const wbout = XLSX.write(workbook, wopts)
+  FileSaver.saveAs(new Blob([s2ab(wbout)], {type: ''}), filename)
+}

+ 2 - 2
src/match/match.js

@@ -8,7 +8,7 @@ class Match extends React.Component {
     const Ort = data[0]
     const Datum = data[1]
     const Zeit = data[2]
-    const Konkurrenz = data[3]
+    const Konkurrenz = data[3].replace(/\s+/, ' ')
     const Spieler1 = data[4]
     const Spieler1Klassierung = data[5]
     const Spieler2 = data[6]
@@ -16,7 +16,7 @@ class Match extends React.Component {
 
     return (
       <li>
-        {Ort}, {Datum}, {Zeit}, {Konkurrenz}, {Spieler1}, {Spieler2}
+        {Ort ? Ort : <strong>Kein Platz zugeteilt</strong>}, {Datum ? Datum : <strong>Kein Datum zugeteilt</strong>}, {Zeit ? Zeit : <strong>Keine Zeit zugeteilt</strong>}, {Konkurrenz}, {Spieler1}, {Spieler2}
       </li>
     )
   }

+ 1 - 1
src/player/player.js

@@ -5,7 +5,7 @@ class Player extends React.Component {
     const data = this.props.player
     const bdate1 = data[7] ? data[7].split('/') : null
     const bdate2 = data[26] ? data[26].split('/') : null
-    const Konkurrenz = data[0]
+    const Konkurrenz = data[0].replace(/\s+/, ' ')
     const Lizenz = data[2]
     const Name = data[5]
     const Vorname = data[6]

+ 56 - 2
yarn.lock

@@ -281,12 +281,16 @@ async@^1.4.0, async@^1.5.2:
   version "1.5.2"
   resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
 
-async@^2.1.2, async@^2.1.4:
+async@^2.1.2, async@^2.1.4, async@latest:
   version "2.4.1"
   resolved "https://registry.yarnpkg.com/async/-/async-2.4.1.tgz#62a56b279c98a11d0987096a01cc3eeb8eb7bbd7"
   dependencies:
     lodash "^4.14.0"
 
+async@~0.2.6:
+  version "0.2.10"
+  resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
+
 asynckit@^0.4.0:
   version "0.4.0"
   resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
@@ -1004,7 +1008,7 @@ binary-extensions@^1.0.0:
   version "1.8.0"
   resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.8.0.tgz#48ec8d16df4377eae5fa5884682480af4d95c774"
 
-blob@0.0.4:
+blob@0.0.4, blob@^0.0.4:
   version "0.0.4"
   resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.4.tgz#bcf13052ca54463f30f9fc7e95b9a47630a94921"
 
@@ -1958,6 +1962,12 @@ duplexer@^0.1.1:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1"
 
+easy-zip@~0.0.4:
+  version "0.0.4"
+  resolved "https://registry.yarnpkg.com/easy-zip/-/easy-zip-0.0.4.tgz#b2da37d6750221860aaef0168de912ebfe957d93"
+  dependencies:
+    async latest
+
 ecc-jsbn@~0.1.1:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505"
@@ -2654,6 +2664,15 @@ fs-extra@^0.30.0:
     path-is-absolute "^1.0.0"
     rimraf "^2.2.8"
 
+fs-extra@~0.5.0:
+  version "0.5.0"
+  resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.5.0.tgz#34646dc62c97fa13b1024946bf4174e8c0f05ca4"
+  dependencies:
+    jsonfile "0.0.x"
+    mkdirp "0.3.x"
+    ncp "0.2.x"
+    rimraf "~2.1.2"
+
 fs.realpath@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
@@ -2786,6 +2805,10 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9:
   version "4.1.11"
   resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
 
+graceful-fs@~1:
+  version "1.2.3"
+  resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-1.2.3.tgz#15a4806a57547cb2d2dbf27f42e89a8c3451b364"
+
 "graceful-readlink@>= 1.0.0":
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
@@ -3705,6 +3728,10 @@ json5@^0.5.0, json5@^0.5.1:
   version "0.5.1"
   resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
 
+jsonfile@0.0.x:
+  version "0.0.1"
+  resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-0.0.1.tgz#b5f9f515121b2844f2cbfe14338b55c79800e1d8"
+
 jsonfile@^2.1.0:
   version "2.4.0"
   resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8"
@@ -4040,6 +4067,10 @@ minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
 
+mkdirp@0.3.x:
+  version "0.3.5"
+  resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.5.tgz#de3e5f8961c88c787ee1368df849ac4413eca8d7"
+
 mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
   version "0.5.1"
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
@@ -4058,6 +4089,15 @@ ms@2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
 
+msexcel-builder@^0.0.2:
+  version "0.0.2"
+  resolved "https://registry.yarnpkg.com/msexcel-builder/-/msexcel-builder-0.0.2.tgz#dec552f0fb6f3bf368e5bd32bd926535677b03d0"
+  dependencies:
+    async "~0.2.6"
+    easy-zip "~0.0.4"
+    fs-extra "~0.5.0"
+    xmlbuilder ">=0.4.2"
+
 mute-stream@0.0.5:
   version "0.0.5"
   resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0"
@@ -4080,6 +4120,10 @@ ncname@1.0.x:
   dependencies:
     xml-char-classes "^1.0.0"
 
+ncp@0.2.x:
+  version "0.2.7"
+  resolved "https://registry.yarnpkg.com/ncp/-/ncp-0.2.7.tgz#46fac2b7dda2560a4cb7e628677bd5f64eac5be1"
+
 negotiator@0.6.1:
   version "0.6.1"
   resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
@@ -5340,6 +5384,12 @@ rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.6.1:
   dependencies:
     glob "^7.0.5"
 
+rimraf@~2.1.2:
+  version "2.1.4"
+  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.1.4.tgz#5a6eb62eeda068f51ede50f29b3e5cd22f3d9bb2"
+  optionalDependencies:
+    graceful-fs "~1"
+
 ripemd160@^2.0.0, ripemd160@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.1.tgz#0f4584295c53a3628af7e6d79aca21ce57d1c6e7"
@@ -6476,6 +6526,10 @@ xml-name-validator@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635"
 
+xmlbuilder@>=0.4.2:
+  version "9.0.0"
+  resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.0.tgz#a9311b3f8509345700c49a8f79be06bcc5988d18"
+
 xmlhttprequest-ssl@1.5.3:
   version "1.5.3"
   resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz#185a888c04eca46c3e4070d99f7b49de3528992d"