使用 excel office 脚本将列拆分为行

Split column into rows using excel office script

我想使用 excel office 脚本将一列拆分为多行,但我不知道该怎么做。

我在 excel 中有一个格式如下的时间表,我想将其分成几列。

Original table

最终table需要这样

Final Table

这是否可以实现,如果可以,有人可以分享代码吗

根据您的描述,我尝试了使用 Office 脚本的解决方案。它需要这样的 table:

并在新工作表上输出新的 table,如下所示:

无论好坏,我都试图通过从第一个 table 派生的公式将逻辑保留在工作簿中并输出到第二个。如果每天超过一个activity,这个公式逻辑就需要重写。

我不是开发人员,但我已经可以看到此 Office 脚本中需要改进的地方:

function main(workbook: ExcelScript.Workbook) {
  // delete new worksheet if it already exists so the rest of the script can run effectively
  // should you need to retain data simply rename worksheet before running
  if (workbook.getWorksheet("My New Sheet") != undefined) {
    workbook.getWorksheet("My New Sheet").delete()
  }
  // assumes your original data is in a table
  let myTable = workbook.getTable("Table1");
  let tableData = myTable.getRangeBetweenHeaderAndTotal().getValues();
  
  // extract the dates as excel serial numbers
  let allDates:number[] = [];
  for (let i = 0; i < tableData.length; i++) {
    allDates.push(tableData[i][2], tableData[i][3]);
  }
  let oldestDate = Math.min(...allDates);
  let newestDate = Math.max(...allDates);
  let calendarSpread = newestDate-oldestDate+2;

  // construct formula from the tableData
  // first add a new 'column' to tableData to represent the days of the week (as numbers) on which the activity is planned. this will be an array added to each 'row'.
  for (let r = 0; r < tableData.length; r++) {
    tableData[r].push(findDay(tableData[r][1]));
  }
  // start a near blank formula string
  let formulaText:string = '=';
  // use the following cell reference
  let cellRef = 'C2';
  // construct the formual for each row in the data and with each day of the week for the row
  let rowCount:number;
  for (let r = 0; r < tableData.length; r++) {
    if (tableData[r][4].length > 1) {
      formulaText += 'IF(AND(OR(';
    } else {
      formulaText += 'IF(AND(';
    }
    for (let a=0; a < tableData[r][4].length; a++) {
      formulaText += 'WEEKDAY(' + cellRef + ')=' + tableData[r][4][a].toString();
      if (a == tableData[r][4].length - 1 && tableData[r][4].length > 1) {
        formulaText += '),';
      } else {
        formulaText += ', ';
      }
    }
    formulaText += cellRef + '>=' + tableData[r][2] + ', ' + cellRef + '<=' + tableData[r][3] + '), "' + tableData[r][0] + '", ';
    rowCount = r+1;
  }
  formulaText += '"-"';
  for (let p=0; p<rowCount; p++) {
    formulaText += ')';
  }

  // create a new sheet
  let newSheet = workbook.addWorksheet("My New Sheet");
  // add the header row
  let header = newSheet.getRange("A1:C1").setValues([["Activity", "Day", "Date"]])
  // insert the oldest date into the first row, then add formula to adjacent cells in row
  let firstDate = newSheet.getRange("C2")
  firstDate.setValue(oldestDate);
  firstDate.setNumberFormatLocal("m/d/yyyy");
  firstDate.getOffsetRange(0, -1).setFormula("=C2");
  firstDate.getOffsetRange(0, -1).setNumberFormatLocal("ddd");
  firstDate.getOffsetRange(0, -2).setFormula(formulaText);
  // use autofill to copy results down until the last day in the sequence
  let autoFillRange = "A2:C" + (calendarSpread).toString();
  firstDate.getResizedRange(0, -2).autoFill(autoFillRange, ExcelScript.AutoFillType.fillDefault);
  // convert the the range to a table and format the columns
  let outputTable = newSheet.addTable(newSheet.getUsedRange(), true);
  outputTable.getRange().getFormat().autofitColumns();
  //navigate to the new sheet
  newSheet.activate();
}

// function to return days (as a number) for each day of week found in a string 
function findDay(foo: string) {
  // start with a list of days to search for
  let daysOfWeek:string[] = ["Sun", "Mon", "Tue", "Wed", "Thur", "Fri", "Sat"];
  //create empty arrays
  let searchResults:number[] = [];
  let daysFound:number[] = [];
  // search for each day of the week, this will create an array for each day of the week where the value is -1 if the day is not found or write the position where the day is found
  for (let d of daysOfWeek) {
    searchResults.push(foo.search(d));
  }
  // now take the search results array and if the number contained is greater than -1 add it's position+1 to a days found array. this should end up being a list of numbered days of the week found in a string/cell
  for (let i = 0; i < searchResults.length; i++) {
    if (searchResults[i] > -1) {
      daysFound.push(i + 1);
    }
  }
  return daysFound
}

对于可能感兴趣的任何人,我设法使用以下代码使它正常工作。

  workbook.getWorksheet('UpdatedSheet')?.delete()
  let usedRange = workbook.getActiveWorksheet().getTables()[0].getRangeBetweenHeaderAndTotal();
  let newString: string[][] = [];

  usedRange.getValues().forEach(row => {
    let daysRows: string[][] = [];
    let days = row[1].toString().split(',');
    days.forEach(cellValue => {
      if (cellValue != ' ') {
        let eachDayData = row.toString().replace(row[1].toString(), cellValue).split(',');
        daysRows.push(eachDayData);
      }
    });
    daysRows.forEach(actualDay => {
      const effDate = new Date(Math.round((actualDay[2] as unknown as number - 25569) * 86400 * 1000))
      const disDate = new Date(Math.round((actualDay[3] as unknown as number - 25569) * 86400 * 1000))

      getDatesInRange(effDate, disDate).forEach(element => {
        let options = { weekday: 'short' }
        if (element.toLocaleDateString('en-GB', options) == actualDay[1]) {
          let datas = actualDay.toString().replace(actualDay[2], element.toDateString()).split(',')
          datas.pop()
          newString.push(datas)
        }
      });
    });
  });
  workbook.getWorksheet('UpdatedSheet')?.delete()
  let workSheet = workbook.addWorksheet('UpdatedSheet');
  workSheet.activate();
  let headers = workSheet.getRange('A1:C1').setValue([['Activity', 'Day', 'Date']])
  let range = workSheet.getRange('A2');
  let resizedRange = range.getAbsoluteResizedRange(newString.length, newString[0].length);
  resizedRange.setValues(newString);

  let tableRange = workSheet.getRange("A2").getSurroundingRegion().getAddress();
  let newTable = workbook.addTable(workSheet.getRange(tableRange), true);
  newTable.setName('updatedTable');
  workSheet.getRange().getFormat().autofitColumns()
}

function getDatesInRange(startDate: { getTime: () => string | number | Date; }, endDate: string | number | Date) {
  const date = new Date(startDate.getTime());
  const dates: Date[] = [];

  while (date <= endDate) {
    dates.push(new Date(date));
    date.setDate(date.getDate() + 1);
  }
  return dates;
}