코딩 수준: 중급
시간: 20분
프로젝트 유형: 편집자 부가기능
목표
- 솔루션의 기능을 이해합니다.
- 애플리케이션 내에서 Apps Script 서비스가 수행하는 작업을 이해
솔루션을 제공합니다
- 스크립트를 설정합니다.
- 스크립트를 실행합니다.
이 솔루션 정보
비어 있는 행과 열을 자동으로 삭제하고
데이터 범위의 가장자리까지 스프레드시트를 자르고 데이터의 공백을 메우는 것과 같습니다.
데이터를 얻을 수 있습니다.
작동 방식
스크립트는 다음 기능을 수행합니다.
- 빈 행 삭제: 선택한 범위 내에서 스크립트는 빈 행을 식별합니다.
행을 삭제합니다. 행의 셀에 공백 문자가 있으면 행
비어 있지 않은 것으로 간주됩니다.
- 빈 열 삭제: 선택한 범위 내에서 스크립트는 빈 열을 식별합니다.
열을 삭제하고 삭제합니다. 열 내의 셀에 공백 문자가 포함된 경우
열은 비어 있지 않은 것으로 간주됩니다.
- 시트를 데이터 범위로 자르기: 스크립트는 데이터 범위가 끝나는 위치를 식별합니다.
초과된 행과 열을 삭제합니다.
- 빈 행 채우기: 스크립트는
선택한 활성 셀을 그 아래 행의 빈 셀로 드래그합니다. 스크립트가 중지됨
비어 있지 않거나 끝에 도달할 때 콘텐츠 붙여넣기
확인할 수 있습니다.
Apps Script 서비스
이 솔루션은 다음 서비스를 사용합니다.
기본 요건
이 샘플을 사용하려면 다음과 같은 기본 요건이 필요합니다.
- Google 계정 (Google Workspace 계정은
관리자의 승인이 필요함)
- 인터넷에 액세스할 수 있는 웹브라우저
스크립트 설정
- 다음 버튼을 클릭하여 클린 시트 Apps Script 프로젝트를 엽니다.
프로젝트 열기
- 개요를 클릭합니다.
info_outline입니다.
- 개요 페이지에서 사본 만들기를 클릭합니다.
- 복사한 프로젝트 상단에서 배포를 클릭합니다.
> 배포 테스트를 클릭합니다.
- 유형 선택 옆에 있는 배포 유형 사용 설정을 클릭합니다.
<ph type="x-smartling-placeholder"></ph>
> 편집자 부가기능으로 이동합니다.
- 새 테스트 만들기를 클릭합니다.
- 테스트 문서에서 선택한 문서 없음을 클릭합니다.
- 정리할 데이터가 포함된 스프레드시트를 선택하고 삽입을 클릭합니다.
샘플 문서의 경우 샘플 정리 데이터 스프레드시트의 사본을 만듭니다.
- 테스트 저장을 클릭합니다.
- 스프레드시트를 열려면 저장된 테스트 옆의 라디오 버튼을 선택하고
실행을 클릭합니다.
스크립트 실행
- 스프레드시트에서
A1:F20
범위를 선택합니다.
- 확장 프로그램 >을 클릭합니다.
클린 시트 사본
> 빈 행을 삭제합니다.
- 메시지가 표시되면 계속을 클릭하고 스크립트를 승인합니다.
- 확장 프로그램 >을 클릭합니다.
클린 시트 사본
> 다시 빈 행을 삭제합니다.
- 확장 프로그램 >을 클릭합니다.
클린 시트 사본
> 빈 열을 삭제합니다.
- 확장 프로그램 >을 클릭합니다.
클린 시트 사본
> 데이터 범위로 시트 자르기:
C7
셀을 선택합니다.
- 확장 프로그램 >을 클릭합니다.
클린 시트 사본
> 아래에서 빈 행 채우기:
코드 검토
이 솔루션의 Apps Script 코드를 검토하려면 다음을 클릭합니다.
아래에서 소스 코드 보기:
소스 코드 보기
Code.gs
// To learn how to use this script, refer to the documentation:
// https://developers.google.com/apps-script/add-ons/clean-sheet
/*
Copyright 2022 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Application Constants
const APP_TITLE = 'Clean sheet';
/**
* Identifies and deletes empty rows in selected range of active sheet.
*
* Cells that contain space characters are treated as non-empty.
* The entire row, including the cells outside of the selected range,
* must be empty to be deleted.
*
* Called from menu option.
*/
function deleteEmptyRows() {
const sheet = SpreadsheetApp.getActiveSheet();
// Gets active selection and dimensions.
let activeRange = sheet.getActiveRange();
const rowCount = activeRange.getHeight();
const firstActiveRow = activeRange.getRow();
const columnCount = sheet.getMaxColumns();
// Tests that the selection is a valid range.
if (rowCount < 1) {
showMessage('Select a valid range.');
return;
}
// Tests active range isn't too large to process. Enforces limit set to 10k.
if (rowCount > 10000) {
showMessage("Selected range too large. Select up to 10,000 rows at one time.");
return;
}
// Utilizes an array of values for efficient processing to determine blank rows.
const activeRangeValues = sheet.getRange(firstActiveRow, 1, rowCount, columnCount).getValues();
// Checks if array is all empty values.
const valueFilter = value => value !== '';
const isRowEmpty = (row) => {
return row.filter(valueFilter).length === 0;
}
// Maps the range values as an object with value (to test) and corresponding row index (with offset from selection).
const rowsToDelete = activeRangeValues.map((row, index) => ({ row, offset: index + activeRange.getRowIndex() }))
.filter(item => isRowEmpty(item.row)) // Test to filter out non-empty rows.
.map(item => item.offset); //Remap to include just the row indexes that will be removed.
// Combines a sorted, ascending list of indexes into a set of ranges capturing consecutive values as start/end ranges.
// Combines sequential empty rows for faster processing.
const rangesToDelete = rowsToDelete.reduce((ranges, index) => {
const currentRange = ranges[ranges.length - 1];
if (currentRange && index === currentRange[1] + 1) {
currentRange[1] = index;
return ranges;
}
ranges.push([index, index]);
return ranges;
}, []);
// Sends a list of row indexes to be deleted to the console.
console.log(rangesToDelete);
// Deletes the rows using REVERSE order to ensure proper indexing is used.
rangesToDelete.reverse().forEach(([start, end]) => sheet.deleteRows(start, end - start + 1));
SpreadsheetApp.flush();
}
/**
* Removes blank columns in a selected range.
*
* Cells containing Space characters are treated as non-empty.
* The entire column, including cells outside of the selected range,
* must be empty to be deleted.
*
* Called from menu option.
*/
function deleteEmptyColumns() {
const sheet = SpreadsheetApp.getActiveSheet();
// Gets active selection and dimensions.
let activeRange = sheet.getActiveRange();
const rowCountMax = sheet.getMaxRows();
const columnWidth = activeRange.getWidth();
const firstActiveColumn = activeRange.getColumn();
// Tests that the selection is a valid range.
if (columnWidth < 1) {
showMessage('Select a valid range.');
return;
}
// Tests active range is not too large to process. Enforces limit set to 1k.
if (columnWidth > 1000) {
showMessage("Selected range too large. Select up to 10,000 rows at one time.");
return;
}
// Utilizes an array of values for efficient processing to determine blank columns.
const activeRangeValues = sheet.getRange(1, firstActiveColumn, rowCountMax, columnWidth).getValues();
// Transposes the array of range values so it can be processed in order of columns.
const activeRangeValuesTransposed = activeRangeValues[0].map((_, colIndex) => activeRangeValues.map(row => row[colIndex]));
// Checks if array is all empty values.
const valueFilter = value => value !== '';
const isColumnEmpty = (column) => {
return column.filter(valueFilter).length === 0;
}
// Maps the range values as an object with value (to test) and corresponding column index (with offset from selection).
const columnsToDelete = activeRangeValuesTransposed.map((column, index) => ({ column, offset: index + firstActiveColumn}))
.filter(item => isColumnEmpty(item.column)) // Test to filter out non-empty rows.
.map(item => item.offset); //Remap to include just the column indexes that will be removed.
// Combines a sorted, ascending list of indexes into a set of ranges capturing consecutive values as start/end ranges.
// Combines sequential empty columns for faster processing.
const rangesToDelete = columnsToDelete.reduce((ranges, index) => {
const currentRange = ranges[ranges.length - 1];
if (currentRange && index === currentRange[1] + 1) {
currentRange[1] = index;
return ranges;
}
ranges.push([index, index]);
return ranges;
}, []);
// Sends a list of column indexes to be deleted to the console.
console.log(rangesToDelete);
// Deletes the columns using REVERSE order to ensure proper indexing is used.
rangesToDelete.reverse().forEach(([start, end]) => sheet.deleteColumns(start, end - start + 1));
SpreadsheetApp.flush();
}
/**
* Trims all of the unused rows and columns outside of selected data range.
*
* Called from menu option.
*/
function cropSheet() {
const dataRange = SpreadsheetApp.getActiveSheet().getDataRange();
const sheet = dataRange.getSheet();
let numRows = dataRange.getNumRows();
let numColumns = dataRange.getNumColumns();
const maxRows = sheet.getMaxRows();
const maxColumns = sheet.getMaxColumns();
const numFrozenRows = sheet.getFrozenRows();
const numFrozenColumns = sheet.getFrozenColumns();
// If last data row is less than maximium row, then deletes rows after the last data row.
if (numRows < maxRows) {
numRows = Math.max(numRows, numFrozenRows + 1); // Don't crop empty frozen rows.
sheet.deleteRows(numRows + 1, maxRows - numRows);
}
// If last data column is less than maximium column, then deletes columns after the last data column.
if (numColumns < maxColumns) {
numColumns = Math.max(numColumns, numFrozenColumns + 1); // Don't crop empty frozen columns.
sheet.deleteColumns(numColumns + 1, maxColumns - numColumns);
}
}
/**
* Copies value of active cell to the blank cells beneath it.
* Stops at last row of the sheet's data range if only blank cells are encountered.
*
* Called from menu option.
*/
function fillDownData() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
// Gets sheet's active cell and confirms it's not empty.
const activeCell = sheet.getActiveCell();
const activeCellValue = activeCell.getValue();
if (!activeCellValue) {
showMessage("The active cell is empty. Nothing to fill.");
return;
}
// Gets coordinates of active cell.
const column = activeCell.getColumn();
const row = activeCell.getRow();
// Gets entire data range of the sheet.
const dataRange = sheet.getDataRange();
const dataRangeRows = dataRange.getNumRows();
// Gets trimmed range starting from active cell to the end of sheet data range.
const searchRange = dataRange.offset(row - 1, column - 1, dataRangeRows - row + 1, 1)
const searchValues = searchRange.getDisplayValues();
// Find the number of empty rows below the active cell.
let i = 1; // Start at 1 to skip the ActiveCell.
while (searchValues[i] && searchValues[i][0] == "") { i++; }
// If blanks exist, fill the range with values.
if (i > 1) {
const fillRange = searchRange.offset(0, 0, i, 1).setValue(activeCellValue)
//sheet.setActiveRange(fillRange) // Uncomment to test affected range.
}
else {
showMessage("There are no empty cells below the Active Cell to fill.");
}
}
/**
* A helper function to display messages to user.
*
* @param {string} message - Message to be displayed.
* @param {string} caller - {Optional} text to append to title.
*/
function showMessage(message, caller) {
// Sets the title using the APP_TITLE variable; adds optional caller string.
const title = APP_TITLE
if (caller != null) {
title += ` : ${caller}`
};
const ui = SpreadsheetApp.getUi();
ui.alert(title, message, ui.ButtonSet.OK);
}
참여자
이 샘플은 Google에서 Google Developer Experts의 도움을 받아 유지관리합니다.
다음 단계