![[JAVA] 정규표현식으로 문제 해결하기](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FysBQI%2FbtrXBXSQzjA%2FpsmiglKB5QZEPpdp7K7F9k%2Fimg.png)
목표
정규 표현식으로 문제 해결하기: 블로그 테마 변경 중 발생한 대참사
문제 발생 개요
인턴을 진행하면서 한동안 블로그에 소홀했습니다. 쌓여있던 소재들을 다듬어 블로그에 작성하려고 합니다.
그간 수많은 블로그를 보면서 많은 것들을 느꼈습니다. 그 중 하나는, 블로그는 다른 사람이 이해하기 좋게 작성해야 한다는겁니다. 내용도 내용이지만 디자인도 중요한 요소 중 하나입니다.
조잡하게 꾸며놓은 것보다 많은 사람들이 사용하는 테마가 훨씬 보기 편했고, 확실히 다크 테마가 눈이 편하다는 느낌을 받았습니다. 개인적으로 VELOG가 읽기 편하더라구요. 마크다운으로 작성된 문서는 읽기도 편하고 필요한 정보가 어디 있을지 예상이가는 것도 좋았던 것 같습니다. 티스토리 스킨을 찾아보던 중, VELOG와 유사한 정상우님의 hELLO 스킨으로 변경하게 되었습니다. 스킨 변경은 쉬웠으나 큰 문제가 발생하게 됩니다.
티스토리로 블로그를 작성해보신 분들은 아시겠지만, 티스토리는 기본 글씨체가 회색입니다.. 저는 이게 싫었고, 항상 글 마지막에 글씨를 검은색으로 뒤덮고 업로드했습니다....... 그리고 다크 테마를 적용하니 다음과 같은 참사가 발생했습니다.
스킨을 변경하면 폰트, 색깔이 자동으로 변하게 됩니다. 하지만, 저는 검은색으로 뒤덮었기 때문에 HTML 태그로 보면 다음과 같습니다.
<p style="text-align: left;" data-ke-size="size18"><span style="color: #000000;"><b>Swagger는 </b><b>OAS(Open Api Specification)를 위한 오픈소스 프레임워크</b>입니다. 즉, <u>API의 문서를 자동</u>으로 정리해주는 것 입니다.</span></p>
<p style="text-align: left;" data-ke-size="size18"><span style="color: #000000;">해당 Swagger를 협업하는 개발자에게 전달하면 Path, Request, Response, 제약 조건 등을 한 번에 알 수 있습니다. API 문서 자동화 뿐만 아니라, Swagger를 통해 파라미터를 넣어보고 <u>테스트</u>를 진행할 수 있습니다. </span></p>
보시는 것처럼 <span style="color: #000000;"> ~~ </span> 태그가 추가되어있습니다. 일부는 옵션에 color: #000000이 추가되어 있습니다.
약 80개의 글의 태그를 하나하나 걷어내는 것은 사실상 불가능에 가깝습니다. (실제로 해보면 약 5분정도 필요했습니다.)
문제 해결 방안
이렇게 뒤덮인 코드는 일정한 규칙을 갖고 있습니다.
1. 본문이 <span style="color: #000000"> </span> 으로 감싸져있거나,
2. 옵션에 color: #000000 이 추가되어 있다.
제가 쓴 글을 되돌아보고, 생각해봐도, 색깔을 강제하는 것은 잘못된 방식입니다.
때마침 인턴을 진행하면서 정규표현식을 활용한 경험이 있고, 이를 정규표현식을 통해 제거하기로 했습니다.
JAVA의 정규표현식
JAVA에서 정규표현식은 Pattern 클래스와 Matcher 클래스로 구성됩니다. 두 클래스는 생성자가 없다는 특징을 갖고 있습니다.
Pattern 클래스를 통해 정규표현식(Regex)를 작성하고, Matcher 클래스를 통해 일치하는 문자를 찾을 수 있습니다.
두 클래스와 정규표현식에 대한 내용은 다음 블로그에 자세히 나와있고, 제가 활용한 대표적인 메소드만 살펴보겠습니다.
https://coding-factory.tistory.com/529
[Java] 자바 정규 표현식 (Pattern, Matcher) 사용법 & 예제
정규표현식(Regular Expression)이란 컴퓨터 과학의 정규언어로부터 유래한 것으로 특정한 규칙을 가진 문자열의 집합을 표현하기 위해 쓰이는 형식언어 입니다. 개발을 하다보면 전화번호, 주민등
coding-factory.tistory.com
Pattern 클래스
정규표현식으로 패턴을 생성하거나, 주어진 문자열과 패턴이 일치하는지 확인하는 메소드를 갖고 있습니다.
compile(String regex)
매개변수로 주어진 정규표현식으로 패턴 생성
matcher(CharSequence input)
input으로 들어온 문자열을 통해 Macher 인스턴스 생성
Matcher 클래스
문자열의 패턴을 분석해 정보를 갖고 있습니다. 해당 클래스의 내부를 살펴보면 pattern, from, to, group과 같은 멤버변수를 들고 있습니다. 어떤 패턴으로, 어디부터 어디까지(인덱스) 패턴이 일치하는지에 대한 정보, 패턴이 일치하는 문자 그룹을 갖고 있습니다. 이를 활용하는 대표적인 메소드는 다음과 같습니다.
find(), find(int start)
정규표현식과 일치하는 문자열을 반환합니다. 시작점을 지정할 수도 있습니다.
주의해야 할 점은 단순히 문자열을 반환하는 것이 아닌, 인스턴스 내부에서 가리키고 있는 인덱스가 증가합니다.
즉 패턴과 일치하는 다음 문자열을 가리키게 되어, group()과 같은 메소드를 호출했을 때, 다음 문자열을 반환합니다.
최초 객체가 생성되면 인덱스가 0입니다. 패턴과 일치하는 맨 처음 시퀀스를 갖고 있는 것이 아니기 때문에, 메소드를 활용하기 위해서는 최초에 find() 메소드를 호출해야 합니다. 이러한 특성 때문에 다음과 같은 형태로 많이 사용합니다.
Macher matcher = pattern.matcher(문자열);
while (matcher.find()) {
...
}
group(), group(int group)
정규표현식과 일치하는 문자열 그룹을 반환합니다. group(파라미터)를 0을 주는 경우 아무것도 주지 않은것과 동일하게 패턴을 포함한 내용이 반환되며, 1로 주는 경우 패턴을 제외한 내용만 조회할 수 있습니다.
문제 해결 과정
테스트 코드에 간단하게 작성해서 출력을 해봤으나, 문서가 길어서 복사하기 힘들었습니다.
그래서, API 형태로 개발해 포스트맨 결과값을 그대로 복사하려고 했습니다.
계산 결과값을 받는 API이니, 자연스레 GET 요청을 받는 컨트롤러에 다음과 같이 코드를 작성했습니다.
결과를 얘기하기 전에, 정규표현식부터 짚고 넢어가겠습니다.
Pattern 클래스의 compile 메소드를 통해 패턴을 생성했습니다. (.*?)은 특정 문자열 사이에 무엇이 와도 괜찮다는 의미입니다. 해당 정규표현을 통해 <span> 태그를 통해 색깔을 덮는 것을 제거할 수 있습니다.
또한, 마지막에 String 클래스의 replaceAll 메소드를 통해 "color: #000000"을 모두 빈 문자열로 대체하면서 색깔을 강제하는 옵션을 제거합니다.
해당 API를 호출하면 다음과 같은 결과를 받을 수 있습니다.
HTTP 414 Status를 받을 수 있는데, 몇 백줄이 넘는 HTML 코드를 쿼리스트링으로 넘기니 URI가 너무 길어서 정상적으로 요청을 보내지 못하는 것입니다.
쿼리스트링이 아닌 HTTP Body에 보내기 위해 POST Method를 사용하기로 합니다.
여기서 뜻밖의 문제가 발생하는데, 바로 Escape 문자입니다. HTML 안에 쌍따옴표가 많이 사용되어, body에 작성할 수 없다는 것입니다. 사용하고 있는 IDE(IntelliJ)는 문자열을 복사하면 자동으로 Escape 문자를 추가해줘 문제가 없지만, Postman은 그렇지 않습니다. 따라서, 온라인에 존재하는 Escape 문자를 추가해주는 툴을 사용해 변환하여 넣어야 합니다.
저는 다음 사이트를 활용했습니다.
https://onlinestringtools.com/escape-string
Slash-escape a String - Online String Tools
Simple, free and easy to use online tool that escapes a string. No intrusive ads, popups or nonsense, just a string escaper. Load a string, escape a string.
onlinestringtools.com
이렇게 문자열에 이스케이프 문자를 추가하고, 코드를 다음과 같이 수정했습니다.
@PostMapping("/tag")
public ResponseEntity<String> deleteColorTag(@RequestBody Map<String, String> targetMap) {
String target = targetMap.get("target");
Pattern spanTag = Pattern.compile("<span style=\"color: #000000;\">(.*?)</span>");
Matcher matcher = spanTag.matcher(target);
while (matcher.find()) {
target = target.replaceAll(matcher.group(0), matcher.group(1));
}
return ResponseEntity.ok(target.replaceAll("color: #000000", ""));
}
파라미터를 Map으로 받는 것은 별로 좋아하지 않지만, 이번만 사용할 예정이니 따로 DTO를 생성하지 않았습니다.
이렇게 작성하게 되면 다음과 같은 순서로 진행됩니다.
1.수정 전 HTML 코드 복사
2.포매터를 통해 escape 문자 추가
3.postman에 붙여넣기
4.API 요청
5.게시글 수정
꽤나 복잡한 방법을 70번이나 해야하니, 방법을 최소화하기로 했습니다.
코드를 파라미터로 받지 않고 파일에 저장한 뒤, 파일을 읽어서 파싱 후 반환하는 것으로 수정했습니다.
이렇게 되면, 복잡하게 포매터에 넣고 수정해야할 필요가 없기 때문입니다.
최종 코드는 다음과 같습니다.
@RestController
public class ColorController {
@GetMapping("/tag")
public ResponseEntity<String> deleteColorTag() throws Exception {
Pattern spanTag = Pattern.compile("<span style=\"color: #000000;\">(.*?)</span>");
BufferedReader reader = new BufferedReader(new FileReader("파일이름"));
StringBuilder target = new StringBuilder(new StringBuilder());
String line;
Matcher matcher;
while((line = reader.readLine()) != null) {
matcher = spanTag.matcher(line);
try {
while(matcher.find()){
line = line.replaceAll(matcher.group(0), matcher.group(1));
}
} catch (Exception e) {
target.append("\n").append(line);
continue;
}
target.append("\n").append(line);
}
return ResponseEntity.ok(target.toString().replaceAll("color: #000000", ""));
}
문자열 변환 중에 일부 에러가 발생해도 그냥 결과에 추가하고 수동으로 다듬기로 했습니다.
이로써 변경 전, 후 코드를 관리하기 용이하고 문제도 해결할 수 있게 되었습니다.
결과
이렇게 간단하지만, 정규표현식을 통해 보다 효율적인 방법으로 문제를 해결할 수 있었습니다.
'개발 > JAVA' 카테고리의 다른 글
[JAVA] 객체지향프로그래밍(OOP)의 클래스와 객체 그리고 인스턴스 (2) | 2022.03.16 |
---|---|
[JAVA] 문자열(String)생성, 문자열 비교 Equals(), == 연산자의 차이점 (0) | 2022.01.09 |