정규표현식 - 캡쳐 그룹

수현·2023년 10월 19일

TroubleShooting

목록 보기
1/1
post-thumbnail

문제 상황

템플릿 엔진을 사용하지 않고 html에 데이터를 넣는 상황이다.

const indexTemplate = async (request: HttpRequest) => {
	let contents = (await getFileContents(VIEW_PATH, INDEX_HTML)).toString();

	if (isAuth(request)) {
		const sid = getSID(request);
		contents = contents.replace(
			/\{\{user\}\}/g,
			sessionStorage.storage[sid].nickname,
		);
		contents = contents.replace(/\{\{authBtn\}\}/g, logoutBtn);
	} else {
		contents = contents.replace(/{{user}}/g, 'Web');
		contents = contents.replace(/\{\{authBtn\}\}/g, loginBtn);
	}
	return contents;
};

처음에 위와 같은 코드를 작성했는데 모든 변수를 일일이 작성해서 replace해주어야 해서 지저분해 보인다. 뿐만 아니라 html을 여러 번 읽고 다시 contents에 새로운 문자열을 만들어 저장해야 하므로 시간과 메모리 낭비가 될 수 있다.

해결 과정

위의 상황을 정규표현식의 캡쳐 그룹을 이용하여 html을 한 번만 읽고 데이터를 넣을 수 있도록 바꿔보았다.

const putTemplateData = (
	fileContent: string,
	data: Record<string, string>,
) => {
	return fileContent.replace(/\{\{([^}]+)\}\}/g, (_: string, token: string) => {
		if (token in data) {
			return data[token];
		} else {
			return '';
		}
	});
};

const indexTemplate = async (request: HttpRequest) => {
	const contents = (await getFileContents(VIEW_PATH, INDEX_HTML)).toString();
	const data: Record<string, string> = {};

	if (isAuth(request)) {
		const sid = getSID(request);
		data[templateVars.user] = sessionStorage.storage[sid].nickname;
		data[templateVars.authBtn] = logoutBtn;
	} else {
		data[templateVars.user] = 'Web';
		data[templateVars.authBtn] = loginBtn;
	}

	return putTemplateData(contents, data);
};

일단 'putTemplateData' 함수를 정의하여 인자로 file 내용과 data를 받았다. data는 html에 쓰여진 변수를 key로 하고 들어갈 데이터를 value로 하여 object를 만들었다.

replace 부분을 자세히 봐보자.

fileContent.replace(/\{\{([^}]+)\}\}/g, (_: string, token: string) => {
    if (token in data) {
        return data[token];
    } else {
        return '';
    }
});

정규표현식의 캡쳐 그룹을 사용하여 일치하는 문자를 'token'으로 받고 있다. token을 이용하여 data의 값을 꺼내어 대체 문자열로 return해준다.

이렇게 하면 indexTemplate에서는 data object만 만들어주면 되고 putTemplateVars를 이용해 html에 한 번에 원하는 데이터를 넣을 수 있다. 여기서 포인트였던 캡쳐 그룹과 비캡쳐 그룹에 대해 알아보겠다!

capture group

캡쳐 그룹은 소괄호로 둘러쌓인 부분의 패턴이며 일치하는 텍스트의 일부를 추출하거나 그룹화하는 데 사용된다. 정규표현식을 사용했을 때 반환되는 값은 배열 형태이며 첫 번째 인자는 전체 문자열, 두 번째 부터 캡쳐되어 추출된 문자열들이 나열된다. 예시를 보자.

// generated by chatGPT
const text = "날짜는 2023-10-20입니다.";
const regex = /(\d{4})-(\d{2})-(\d{2})/;
const match = regex.exec(text);  // 배열 반환

if (match) {
    console.log("전체 매치: " + match[0]);
    console.log("년: " + match[1]);
    console.log("월: " + match[2]);
    console.log("일: " + match[3]);
} else {
    console.log("일치하는 패턴을 찾을 수 없습니다.");
}

위에서는 소괄호로 연도, 월, 일을 추출하고 있다. index 0은 전체 문자열, 이후 부터 추출된 문자열이 나열되어 있음을 알 수 있다.

none-capture group

비캡쳐 그룹은 그룹화를 수행하지만 일치된 부분을 캡쳐하지 않는다. 즉, 문자열을 추출하여 반환하지 않는다. 캡쳐 그룹과 같이 소괄호를 사용하지만 안에 ?:를 넣어 구분한다. 아래 예시를 살펴보자.

// generated by chatGPT
const text = "Hello World";
const regex = /(?:Hello) (World)/;
const match = regex.exec(text);

if (match) {
    // 첫 번째 그룹은 캡처되지 않으므로, match 배열의 두 번째 요소에 "World"가 저장됨
    console.log("전체 매치: " + match[0]);
    console.log("두 번째 그룹: " + match[1]);
} else {
    console.log("일치하는 패턴을 찾을 수 없습니다.");
}

정규표현식을 보면 캡쳐 그룹과 비캡쳐 그룹을 둘 다 사용하였다. 'Hello'는 비캡쳐 그룹이므로 배열에 반환되지 않고, 'World'는 캡쳐 그룹이므로 반환된 것을 확인할 수 있다.


참고 자료

https://blog.rhostem.com/posts/2018-11-11-regex-capture-group

profile
실패와 성장을 기록합니다 🎞️

0개의 댓글