Microsoft 365 Excel: Update Row

Microsoft 365 Excel: Update Row

Microsoft 365 Excel: 行更新

Updates data in a specified row of a sheet.

Auto Step icon
Configs for this Auto Step
conf_OAuth2
C1: OAuth2 Setting *
conf_Url
C2: Target Book URL *
conf_Title
C3: Target Sheet Title *#{EL}
conf_RowNo
C4: Row No to update new row(When empty, the top row)
conf_Range
C5: Column Range to Update (e.g. “A:O”)(Up to 15 columns) *
conf_Column1
C6_1: 1st Column Value of updated Row#{EL}
conf_Column2
C6_2: 2nd Column Value of updated Row#{EL}
conf_Column3
C6_3: 3rd Column Value of updated Row#{EL}
conf_Column4
C6_4: 4th Column Value of updated Row#{EL}
conf_Column5
C6_5: 5th Column Value of updated Row#{EL}
conf_Column6
C6_6: 6th Column Value of updated Row#{EL}
conf_Column7
C6_7: 7th Column Value of updated Row#{EL}
conf_Column8
C6_8: 8th Column Value of updated Row#{EL}
conf_Column9
C6_9: 9th Column Value of updated Row#{EL}
conf_Column10
C6_10: 10th Column Value of updated Row#{EL}
conf_Column11
C6_11: 11th Column Value of updated Row#{EL}
conf_Column12
C6_12: 12th Column Value of updated Row#{EL}
conf_Column13
C6_13: 13th Column Value of updated Row#{EL}
conf_Column14
C6_14: 14th Column Value of updated Row#{EL}
conf_Column15
C6_15: 15th Column Value of updated Row#{EL}
Script (click to open)

// OAuth2 config sample at [OAuth 2.0 Setting]
// - Authorization Endpoint URL: https://login.microsoftonline.com/common/oauth2/v2.0/authorize
// - Token Endpoint URL: https://login.microsoftonline.com/common/oauth2/v2.0/token
// - Scope: https://graph.microsoft.com/Files.ReadWrite.All offline_access
// - Consumer Key: (Get by Microsoft Azure Active Directory)
// - Consumer Secret: (Get by Microsoft Azure Active Directory)

const GRAPH_URI = "https://graph.microsoft.com/v1.0/";

const COLUMN_NUM = 15;

main();
function main(){
  //// == Config Retrieving / 工程コンフィグの参照 ==
  const oauth2 = configs.get( "conf_OAuth2" ) + "";
  const bookUrl = retrieveBookUrl();
  const sheetName = configs.get( "conf_Title" ) + "";
  if(sheetName === "" || sheetName === null){
    throw "Sheet Title is empty.";
  }

  const rowNo = retrieveRowNo();

  const range = configs.get( "conf_Range" );

  let values = [];
  retrieveValues( range, values );

  //// == Calculating / 演算 ==
  // Access to the API 1st(Get Book Info)
  const bookInfo = getFileInfoByUrl( bookUrl, oauth2 );
  const worksheetId = getWorksheetId(bookInfo, sheetName, oauth2);

  // Access to the API 2nd(PATCH)
  patchData( bookInfo, worksheetId, rowNo, values, range, oauth2);
}

/**
  * config からブックの URL を読み出す、空ならエラー
  * @return {String} ブックの URL
  */
function retrieveBookUrl() {
  const bookUrlDef = configs.getObject( "conf_Url" );
  let bookUrl;
  if ( bookUrlDef === null ) {
    bookUrl = configs.get( "conf_Url" )
  }else{
    bookUrl = engine.findData( bookUrlDef );
  }
  if ( bookUrl === "" || bookUrl === null){
    throw "Book URL is empty."
  }
  return bookUrl;
}

/**
  * config から行番号の値を読み出す、空なら1
  * 数字でない文字が入っているか、シートの範囲外である数字の場合はエラー
  * シートの範囲: 1 ~ 1048576
  * @return {Number} 行番号
  */
function retrieveRowNo() {
  const rowNoDef = configs.getObject( "conf_RowNo" );
  let rowNo;
  if ( rowNoDef === null ) {
    rowNo = configs.get( "conf_RowNo" )
  } else {
    rowNo = engine.findData( rowNoDef );
  }
  if ( rowNo === "" || rowNo === null){
    rowNo = "1";
  }
  const reg = new RegExp( '^[1-9][0-9]*$' );
  if ( !reg.test( rowNo ) ){
    throw "Row no must be a positive integer.";
  }
  rowNo = parseInt(rowNo, 10); //parse to int
  if ( rowNo > 1048576 ){
    throw "Row no must be 1048576 or below.";
  }
  return rowNo;
}

/**
  * 指定された範囲を考慮しつつ更新データを準備
  * 範囲を優先し、範囲外の指定データ項目は無視する
  * @param {Array} range  範囲の配列
  * @param {Array} values  更新データの配列
  */
function retrieveValues( range, values ) {

  //rangeの範囲に合わせてデータを準備、最大数の制限あり
  const rangeArr = range.split(':');
  const from = convertA1toR1C1(rangeArr[0] + "");
  const to = convertA1toR1C1(rangeArr[1] + "");
  const target = to - from + 1;
  let limit = COLUMN_NUM;
  if (target <= COLUMN_NUM) {
    limit = target;
  }
  for (let i = 0; i < limit; i++) {
    const columnConfigName = `conf_Column${i+1}`;
    let columnValue = configs.get( columnConfigName );
    //nullだった場合は空文字で上書きする
    if (!columnValue) {
      columnValue = "";
    }
    values.push( columnValue );
  }
}

/**
  * フォルダの URL からファイル情報(ドライブ ID とファイル ID)を取得し、
  * オブジェクトで返す(URL が空の場合はエラーとする)
  * @param {String} fileUrl  フォルダの URL
  * @param {String} oauth2  OAuth2 設定
  * @return {Object} fileInfo  ファイル情報 {driveId, fileId}
  */
function getFileInfoByUrl( fileUrl, oauth2 ) {
  let fileInfo;
  if ( fileUrl !== "" && fileUrl !== null ) {
    // 分割代入
    const {
      id,
      parentReference: {
        driveId
      }
    } = getObjBySharingUrl( fileUrl, oauth2 );
    fileInfo = {driveId: `drives/${driveId}`, fileId: id};
  }
  return fileInfo;
}

/**
  * OneDrive のドライブアイテム(ファイル、フォルダ)のメタデータを取得し、JSON オブジェクトを返す
  * API の仕様:https://docs.microsoft.com/ja-jp/onedrive/developer/rest-api/api/shares_get?view=odsp-graph-online
  * @param {String} sharingUrl  ファイルの共有 URL
  * @param {String} oauth2  OAuth2 設定
  * @return {Object} responseObj  ドライブアイテムのメタデータの JSON オブジェクト
  */
function getObjBySharingUrl( sharingUrl, oauth2 ) {
  if (sharingUrl === "" || sharingUrl === null) {
    throw `Sharing URL is empty.`;
  }

  // encoding sharing URL
  const encodedSharingUrl = encodeSharingUrl(sharingUrl);

  // API Request
  const response = httpClient.begin()
    .authSetting( oauth2 )
    .get( `${GRAPH_URI}shares/${encodedSharingUrl}/driveItem` );

  const responseStr = logAndJudgeError(response, "GET");

  return JSON.parse( responseStr );
}

/**
  * 共有URLをunpadded base64url 形式にエンコードする
  * @param {String} sharingUrl  共有 URL
  * @return {String} encodedSharingUrl  エンコードされた共有 URL
  */
function encodeSharingUrl( sharingUrl ) {
  let encodedSharingUrl = base64.encodeToUrlSafeString( sharingUrl );
  while ( encodedSharingUrl.slice(-1) === '=' ) {
    encodedSharingUrl = encodedSharingUrl.slice(0,-1);
  }
  return `u!${encodedSharingUrl}`;
}

/**
  * ワークシートの ID を取得する
  * @param {Object} bookInfo
  * @param {String} bookInfo.driveId  ワークブックのドライブ ID
  * @param {String} bookInfo.fileId  ワークブックのファイル ID
  * @param {String} sheetName  シートの名前
  * @param {String} oauth2  OAuth2 設定
  */
function getWorksheetId({driveId, fileId}, sheetName, oauth2) {
    const getWorksheetsUrl = `${GRAPH_URI}${driveId}/items/${fileId}/workbook/worksheets`;
    const response = httpClient.begin()
        .authSetting(oauth2)
        .get(getWorksheetsUrl);

    const responseStr = logAndJudgeError(response, "2nd GET");
    const jsonRes = JSON.parse(responseStr);
    const worksheet = jsonRes.value.find(worksheet => worksheet.name === sheetName);
    if (worksheet === undefined) {
        throw 'Worksheet not found.';
    }
    return worksheet.id;
}

/**
  * 指定シートの指定行のデータを更新する
  * @param {Object} bookInfo
  * @param {String} bookInfo.driveId  ワークブックのドライブ ID
  * @param {String} bookInfo.fileId  ワークブックのファイル ID
  * @param {String} worksheetId  シートの ID
  * @param {Number} rowNo  更新箇所の行番号
  * @param {Array} values  更新するデータの配列
  * @param {String} range  更新する列範囲
  * @param {String} oauth2  OAuth2 設定
  */
function patchData( {driveId, fileId}, worksheetId, rowNo, values, range, oauth2 ){

  const rangeArr = range.split(':');

  const patchUri = `${GRAPH_URI}${driveId}/items/${fileId}/workbook/worksheets/${encodeURIComponent(worksheetId)}/range(address='${rangeArr[0]}${rowNo}:${rangeArr[1]}${rowNo}')/`;
  const requestBody = makeRequestToAdd(values);
//for debug
//engine.log("uri:" + patchUri)
//engine.log("json:" + JSON.stringify(requestBody))

  const response = httpClient.begin()
    .authSetting( oauth2 )
    .body( JSON.stringify(requestBody), "application/json" )
    .patch( patchUri );
  
  logAndJudgeError(response, "PATCH");
}

/**
  * 更新するデータを、JSON 形式に変換する
  * @param {Array} values  データの入った配列
  * @return {JSON Object} 変換した JSON オブジェクト
  */
function makeRequestToAdd(values){
  let request = {
    values : [[]]
  };
  for (let i = 0; i < values.length; i++) {
    //nullではなく空文字の場合は空文字で上書きする
//    if(values[i] === "" || values[i] === null){
    if(values[i] === null){
      request.values[0].push(null);
    } else {
      if(values[i].length > 32767){
        throw "Can't set text over 32,767 character.";
      }
      request.values[0].push(values[i]);
    }
  }
  return request;
}

/**
  * 列を指定するアルファベットを数値変換する(例:A→1、C→3)
  * @param {String} columnString 列を指定するアルファベット
  * @return {Number} num アルファベットに対応した数値
  */
function convertA1toR1C1(columnString) {
  const RADIX = 26;
  const A = 'A'.charCodeAt(0);
  let str = columnString.toUpperCase();
  let num = 0;
  let strLength = str.length;
  for (let i = 0; i < strLength; i++) {
    num = (num * RADIX) + (str.charCodeAt(i) - A + 1);
  }
  return num;
}

/**
  * ログの出力と、エラー発生時のスローを行う
  * @param {HttpResponseWrapper} response  リクエストの応答
  * @param {String} requestType リクエストをどの形式で行ったか("GET" or "POST" or "PATCH")
  * @return {String} responseStr レスポンスの文字列
  */
function logAndJudgeError(response, requestType){
  const responseStr = response.getResponseAsString();
  const status = response.getStatusCode();
  if(status >= 300){
    const accessLog = `---${requestType} request--- ${status}\n${responseStr}\n`;
    engine.log(accessLog);
    throw `Failed in ${requestType} request. status: ${status}`;
  }
  return responseStr;
}

Download

warning Freely modifiable JavaScript (ECMAScript) code. No warranty of any kind.
(Installing Addon Auto-Steps are available only on the Professional edition.)

Notes

  • About the settings for integration with Microsoft365 services
  • In cases where there is a mismatch between the column range to be added and the specification of the value to be added on line 0 of the tail line, the column range to be added takes precedence.
    For example, if the column range is specified as “A:C” (for 3 lines), the values to be added after the fourth line are ignored.
  • An error may occur if the sheet name contains parentheses or other symbols. If an error occurs, consider changing the sheet name.

Capture

See Also

Scroll to Top

Discover more from Questetra Support

Subscribe now to keep reading and get access to the full archive.

Continue reading