🍄RawData

💾 google apps script

// 📅 revision history:
// 2022.07.28: 
// - 將 mainColumns 放入 constructor() 的參數,存入 RawData
// - indexOfColumn 改存放於 RawData
// - _cell() 改為 cellValue()
// - 🐞 除蟲: Object.entries(this.data) 改為 this.data.entries() - (integer-base index)

// 🍄 RawData
// 負責分析原始資料
// 🔸 data: [[string]]          // 原始資料(全部轉為字串)
// 🔸 mainColumns: [string]     // 主要欄位(⭐ 由這些字串「開頭(startWith)」即可,不需一模一樣。)
// 🔸 indexOfColumn (dict)      // 共有欄位索引
class RawData {

  // custom class type name
  get [Symbol.toStringTag]() { return 'RawData' }

  // init
  constructor(sheetName, mainColumns){      // data from data range values

    // 🔸 .mainColumns
    this.mainColumns = mainColumns;

    let data = app.dataRangeValuesFromSheet(sheetName);
    if(!data) throw new Error(`⛔ RawData: 找不到試算表「${sheetName}」的資料`);

    // 🔸 .data
    // normalize all cell values (remove all whitespaces)
    this.data = data.map(row => 
      row.map(value => (value + '').removeWhitespaces())
    ); 

    // 🔸 .indexOfColumn
    this.parseColumnIndices();     // parse phase 1: 找共有欄位索引

    // parse phase 2: 逐列抓出各班學生資料
    // 🔸 app.students
    this.parseAllRows();                 
  }

  // parse main column indices
  parseColumnIndices(){

    for(const [i, row] of this.data.entries()){

      // if not header row, skip
      if(!this.isHeaderRow(row)) continue;   

      // 🔸 .indexOfColumn
      // now, it's a header row
      this.indexOfColumn = this._indexDictForMainColumnsFromRow(row);

      const n = Object.keys(this.indexOfColumn).length;
      log(`ℹ️ 第 ${i + 1} 列:「${row}」`);
      log(`ℹ️ 在第 ${i + 1} 列找到主要欄位「${this.mainColumns}」(共 ${n} 欄),索引值如下:`);
      log(this.indexOfColumn);

      // braak for-loop
      break;    
    }

    // if not found, throw error
    if(!this.indexOfColumn) throw new Error(
      `⛔ RawData._parseColumnIndices(): 在「原始報表」中找不到主要欄位「${this.mainColumns}」`
    );
  }

  // find indices for main cols from row
  _indexDictForMainColumnsFromRow(row){
    
    let dict = {};
    
    for(const colName of this.mainColumns){
        const i = row.findIndex(x => x.startsWith(colName));  // ⭐️ 例如:原始「科目」名稱後面還有「編號」!
        if(i < 0) throw new Error(`⛔ RawData._indexDictForMainColumnsFromRow:「${row}」沒有「${colName}」這個欄位名稱。`);
        dict[colName] = i;
    }
    
    return dict;
  }

  // 🔸 儲存格資料:(for main columns)
  cellValue(row, colName) {
    return row[this.indexOfColumn[colName]];
  }

  // 🔸 parse all students data from rows
  parseAllRows(){
    for(const row of this.data){
      if(this.isStudentRow(row)) Student.all.push(this._studentFromRow(row));
    }
    log(`ℹ️  共找到 ${Student.all.length} 個學生資料。`);
    log(`ℹ️  第一個學生:${Student.all[0]}`);
  }

  // new student from row
  _studentFromRow(row) {
    return new Student(...this.mainColumns.map(col => this.cellValue(row, col)));
  }

  // 🔸 是否為表頭列(含有班級、座號、姓名等)
  isHeaderRow(row){
    // 對於每個主要欄位名稱(name),本列都有一個元素值(value)是以這個名稱開始的(startsWith)。
    return this.mainColumns.every(name => row.some(value => value.startsWith(name)));
  }

  // 🔸 is student row
  isStudentRow(row){

    const classNo = +this.cellValue(row, '班級');
    const seatNo = +this.cellValue(row, '座號');
    if(!(100 < classNo && classNo < 320)) return false;   // 不是班級
    if(!(0 < seatNo && seatNo < 70)) return false;        // 不是座號

    if(!this.cellValue(row, '姓名')) return false;         // 不是姓名

    const rank = +this.cellValue(row, '名次');
    if(!(0 < rank && rank < 4)) return false;             // 不是前三名

    return true;
  }
  
}

Last updated