이번글에서는 RegExp 객체가 가지고 있는 세 개의 메소드 (Method) 들 중에서 Replace() 메소드에 관해서 중점적으로 살펴보도록 하겠다. 쉽게 예상할 수 있겠지만 RegExp.Replace() 메소드는 작업 대상 문자열에서 지정한 정규 표현식 패턴을 만족하는 문자열(들)을 찾고 그 찾아낸 문자열(들)을 지정한 문자열로 치환한다.
다음의 코드는 ASP 상에서 Replace() 메소드를 사용하기 편리하도록 언제나처럼 필자가 미리 함수로 만들어 놓은 것으로서 역시 필요하신 분들께서는 각자의 상황에 알맞게 수정하여 사용하면 된다. 그리고, 이 코드는 이전글에서 Test() 메소드와 Execute() 메소드를 설명하기 위해서 만들었던 두 개의 함수, 즉 RegExpTest() 함수와 RegExpExec() 함수를 통해서 이미 익숙해진 패턴을 거의 그대로 따르고 있으므로 특별히 추가적인 설명은 필요없을 것이라고 믿고 바로 글을 진행하도록 하겠다.
<% |
그렇다면 이제 실제로 이 함수를 사용하여 실무에서 빈번히 발생할 수 있을 듯한 사례를 처리해 보도록 하자. 다음은 Taeyo's ASP & .NET 의 Q & A 메뉴 중, ASP 게시판 에서 발췌한 하나의 실제 사례이다. 이 질문의 원문을 올리셨던 분께는 필자가 E-Mail 로 인용에 대한 양해를 드렸으며 그 분께서는 별다른 조건없이 너무도 흔쾌히 인용을 허락을 해주셨다. 이 자리를 빌어서 다시 한 번 감사드린다.
이제 이 사례의 경우 발생한 문제점이 무었이었는지 다음의 글을 한 번 살펴보자.
데이타 베이스 내에서 가져올때 변환을 시켜서 가져올라고 하는데 좀처럼 쉽지 않네요....... 예를 들어 데이타 내에 테이블 사이즈인 width=500 이런것들이 불규칙하게 존재합니다...... 그런데 그 데이타를 가져올때 테이블 크기땜에 자꾸 삐져나갑니다..... 그래서 Replace 함수를 써서 조금 변형 되게 가져 올라고 하는데.....특정한건 되지만 예를 들어 width 가 500 보다 큰것은 전부 500으로 바꾸어 줄라면 어떠케 해야 하나요?? 데이타를 가져올때의 문제인가요??아님 방법이 없는걸까요?? 도와주세요..........^^ |
즉 현재 데이타베이스의 특정 필드 (Filed) 에는 HTML 과 일반 문자열들이 혼합된 일련의 문장들이 저장되어져 있고, 이 문장들을 가져와서 미리 준비된 HTML Template 의 특정 위치에 출력하는 작업이 요구되는 상태이다. 그러나 한 가지 문제가 있는데 가져와야 될 문자열 내부에는 HTML Tag 들이 불규칙하게 섞여 있고 이 중 일부 HTML Tag 에 설정된 Width 어트리뷰트 (Attribute) 의 값 때문에 최종 출력물의 레이아웃 (Layout) 이 깨지는 현상이 발생하는 것이다.
이같은 상황은 게시판의 내용보기 페이지 등에서 HTML Tag 의 출력을 허용하는 경우라든가 또는 ASP.NET 의 Code Behind 와 같이 유지, 보수를 용이하게 하기 위한 목적으로 ASP 코드와 HTML 문서를 분리하기 위하여 자체적으로 HTML Template 을 정의하여 사용하는 경우 매우 흔하게 접할 수 있다. 이런 경우 대부분 문제가 되는 쪽은 기술적인 측면보다는 데이타베이스에서 가져온 HTML Tag 와 일반 문자열들의 혼합 문자열을 HTML Template 에 출력할 때 이 HTML Template 의 레이아웃이 파괴되어 버리는 현상이다.
가령 프로그래머는 폭 500 Pixel 의 테이블 안에 모든 내용을 출력하려는 생각으로 프로그램을 개발했다고 생각해보자. 그러나 실제로 사용자가 입력한 문자열 안에는 폭 600 Pixel 의 크기를 가진 Table Tag 가 포함되어져 있다면 결과적으로 폭 500 Pixel 의 테이블 내부에 폭 600 Pixel 인 테이블이 위치하게 되는 결과가 된다. 따라서 최종 출력물의 레이아웃이 일그러지는 것은 매우 당연한 일이고 위의 사례도 결국은 이와 같은 범주에 속한다고 말할 수 있을 것이다.
이와 동일한 사례를 설명하기 위해 다음과 같이 간단한 약식의 HTML 문자열을 가정해 보도록 하겠다. 지면 관계상 이처럼 작업 대상 문자열을 매우 짧고 간단하게 구성하였으나, 실제로는 보다 긴 문자열이나 HTML Tag 또는 일반 문자열들이 복잡하게 얽힌 문자열을 대상으로 하더라도 지금부터 설명하는 정규 표현식 처리의 결과는 동일하다.
<table border=0 cellpadding=0 cellspacing=0 width=550> <table border=0 cellpadding=0 cellspacing=0 width=500> <table border=0 cellpadding=0 cellspacing=0 width=450> <table border=0 cellpadding=0 cellspacing=0 width=600> <table border=0 cellpadding=0 cellspacing=0 width="650"> <table border=0 cellpadding=0 cellspacing=0 width='750'> |
각 Table Tag 의 가장 끝 부분에 위치한 'Width' 어트리뷰트를 주의해서 보기 바란다. 여러가지 경우를 가정하기 위해 어트리뷰트의 값이 ''' 나 '"' 로 둘러쌓인 경우와 그렇지 않은 경우를 모두 포함시켰다. 일단은 가장 단순한 방법을 사용하여 이 문제를 해결해 보기로 한다. 즉 RegExp.Replace() 메소드만을 사용하고 'Width' 어트리뷰트의 값이 500 보다 큰 경우 무조건 값을 500 으로 치환하는 경우를 가정해보겠다.
결론부터 말해서 위의 RegExpReplace() 함수를 사용하여 이 문제를 해결하기 위한 코드는 다음과 같다. 조금 복잡하게 보이겠지만 알고 나면 간단하므로 너무 걱정하지 말기 바란다. 그리고 한 번 더 강조하지만 이 코드에 사용된 정규 표현식 패턴은 다만 필자가 이렇게 구성했다는 것일 뿐 이 문제에 대한 오직 하나뿐인 정답은 절대 아니며 이보다 더 효율적인 정규 표현식 패턴도 얼마든지 만들어 낼 수 있다는 점을 기억하기 바란다.
RegExpReplace("(<table.*width=(?:|'|""))([56789]d{2}|d{4})((?:|'|"").*>)", Sample_String, "$1500$3")
이제 이러한 결과가 나오기까지의 과정을 한 번 차분히 살펴보도록 하자. 당면한 문제는 결국 세 자리 혹은 네 자리로 된 숫자들을 비교하고 500 보다 큰 경우 500 으로 치환해 주어야 하는데 거슬리는 부분은 숫자라고해서 아무 숫자나 막 바꿀수가 없다는 것이다. 즉 HTML Tag 가 아닌 일반 문자열 중에서도 숫자값이 있을 수 있고 HTML Tag 중에서도 특정 Tag 의 숫자만 바꾸어야 하는 경우도 있다. 이에 더하여 하나의 HTML Tag 내에서도 여러 개의 어트리뷰트들이 비슷한 숫자값을 가지는 경우가 허다하다.
따라서 우리가 원하는 HTML Tag 에서 원하는 어트리뷰트의 값만 비교하여 치환을 해주어야 하는데 바로 이것이 난제인 것이다. 먼저 우리의 관심의 대상인 Width=xxx 부분을 살펴보도록 하자. 필자는 이 부분을 다음과 같이 구성하였다.
width=(?:|'|")([56789]d{2}|d{4})(?:|'|")
당연히 이 정규 표현식 패턴의 가장 처음 부분은 width= 로 시작된다. 그리고 그 다음 부분에는 실제 숫자값이 위치하게 되는데 여기에서 한 가지 고려해야 할 사항이 있다. 실제로 현업에서 HTML 문서를 작성할 때는 width=xxx, width='xxx', 그리고 width="xxx" 와 같은 세 가지 유형의 어트리뷰트 값 표현 방식들이 모두 비슷한 빈도로 사용된다는 점이다. 사견으로 이러한 특성은 HTML 의 가장 나쁜 일면 중 하나라고 생각하는데 이러한 특성은 결국 HTML 문서의 유효성 (Validation) 을 엉망으로 만드는 원인이 되기 때문이다.
따라서 정규 표현식 패턴을 작성할 때에도 이런 점을 고려해 주어야만 하고 필자는 이를 (?:|'|") 라는 정규 표현식 패턴으로 구성했다. 즉 아무것도 없거나 ' (작은따옴표) 이거나 " (큰따옴표) 인 경우를 찾는 것이다. 이 때 한 가지 주의해야 할 점은 VBScript 에서 " 는 문자열을 의미하는 리터럴이므로 실제로 RegExpReplace() 함수에서 사용할 때는 "" 로 바꿔주어야 한다는 것이다.
그리고 이 부분에서 단순히 소괄호를 사용하지 않고 ?: 메타 문자를 사용하여 (?:...) 형식으로 표현한 것은 지난 글에서도 한 번 설명했지만 역참조를 사용하지 않기 위한 것이다. 일단 이 부분에 대해서는 잠시 설명없이 넘어가도록 한다.
이번엔 실제로 치환이 이루어져야 할 숫자 부분을 살펴보자. 현재 조건을 고려해 볼 때 3 자리 이하의 숫자는 치환할 필요가 없으며 5 자리 이상의 숫자도 일반적으로 유통되는 그래픽 카드에서 지원되는 해상도를 고려해 볼 때 그다지 큰 의미가 없다. 따라서 결국 3 자리 숫자 또는 4 자리 숫자만 고려하면 된다는 결론인데 이와 같은 조건은 ([56789]d{2}|d{4}) 이라는 정규 표현식 패턴으로 표현이 가능하다. 즉 5 에서 9 사이의 숫자로 시작되는 모든 3 자리 숫자, 또는 모든 4 자리 숫자를 찾는 것이다. 결국 이와 같이 구성된 정규 표현식 패턴의 조각들을 모두 조합하면 width=(?:|'|")([56789]d{2}|d{4})(?:|'|") 이라는 정규 표현식을 얻을 수 있다.
이제 그 다음 나머지 부분들에 관해 생각해 보자. 지금 우리에게 필요한 것은 무조건 문자열 내부에 존재하는 모든 width="xxx" 스타일의 문자열을 치환하는 것이 아니다. 단지 Table Tag 내부에 존재하는 width="xxx" 스타일의 문자열만을 치환해야 하는 것이다. 따라서 정규 표현식 <table.* 와 .*> 를 위에서 만든 정규 표현식의 앞뒤에 추가하면 다음과 같은 결과가 나오게 된다.
<table.*width=(?:|'|")([56789]d{2}|d{4})(?:|'|").*>
지금까지의 과정을 모두 잘 이해했다면 이제 정규 표현식 전체을 통틀어서 가장 흥미로운 기능이라고 말할 수 있는 역참조 기능에 대해서 알아봐야 될 순서다. 위와 같이 구성된 정규 표현식 패턴 그 자제만으로는 당연히 치환이 불가능하다. 치환할 대상 문자열을 찾기위해서 이처럼 정규 표현식 패턴을 구성했지만 아직까지 찾아낸 대상 문자열을 어떻게 치환할 것인가는 결정하지 않았기 때문이다. 정규 표현식에서는 패턴과 일치하는 문자열들을 찾아 놓고서 찾은 문자열의 일부분들을 조합하여 치환에 재사용할 수가 있는데 이러한 기능을 '역참조' 라고 한다. 만약 정규 표현식에서 이 역참조가 불가능했다면 문자열 처리 작업에 정규 표현식을 사용해야 할 이유가 크게 줄어들었을 것이다.
일반적으로 대상 문자열에서 치환을 하고자 하는 부분은 패턴 전체가 아니라 패턴의 일부분인 경우가 많고 지금의 사례와 같은 경우도 마찮가지다. 우리가 치환하기를 원하는 부분은 <Table ... > Tag 전체가 아니라, 단지 그 중 일부인 width="xxx" 일 뿐이다. 이와 같은 경우 역참조 기능이 매우 유용하게 사용되는데 다음을 보도록 하자. 위에서 작성한 정규 표현식을 크게 세 부분으로 나누었다.
<table.*width=(?:|'|") + ([56789]d{2}|d{4}) + (?:|'|").*>
위와 같은 세 부분 중에서 우리가 치환하기를 원하는 부분은 단지 가운데 부분뿐이다. 오히려 처음 부분과 끝 부분은 바뀌면 절대 안된다. 이 정규 표현식을 다음과 같이 각각 소괄호로 묶어보자.
(<table.*width=(?:|'|")) + (([56789]d{2}|d{4})) + ((?:|'|").*>)
그리고, 다시 합친다.
(<table.*width=(?:|'|"))(([56789]d{2}|d{4}))((?:|'|").*>)
이제 정규 표현식이 완성되었다. 이와 같이 소괄호를 잘 사용하면 재미있는 작업이 가능해진다. 이 정규 표현식 패턴으로 RegExp.Replace() 메소드를 실행시키면 패턴에 매치되는 각각의 문자열을 찾을 때마다 미리 약속된 변수명 $n (여기에서 n 은 1, 2, 3 ...) 으로 패턴의 소괄호안에 있는 문자열들을 순서대로 저장해둔다.
즉, 위의 사례의 경우 가장 첫 번째로 패턴과 일치하는 문자열은 <table border=0 cellpadding=0 cellspacing=0 width=550> 이 될 것이다. 이 때 패턴에 사용된 소괄호를 따져보면 다음과 결론을 얻을 수 있다.
$1 이라는 변수에 문자열 "<table border=0 cellpadding=0 cellspacing=0 width=" 이 저장된다.
$2 이라는 변수에 문자열 "550" 이 저장된다.
$3 이라는 변수에 문자열 ">" 이 저장된다.
그리고, 더욱 고무적인 사실은 이 $n 변수를 바로 아래의 Code 에서와 같이 치환할 문자열에서 사용할 수 있다는 점이다. 아래에서 파랗게 강조된 부분이 바로 치환할 문자열을 나타내는 부분인데 결국 이 문자열은 역참조 변수 $1 과 바꾸려는 값 500 그리고 또 하나의 역참조 변수 $3 이 결합된 문자열인 것이다.
RegExpReplace("(<table.*width=(?:|'|""))([56789]d{2}|d{4})((?:|'|"").*>)", Sample_String, "$1500$3")
따라서 방금 예로 들었던 가장 첫 번째로 패턴과 일치하는 문자열 <table border=0 cellpadding=0 cellspacing=0 width=550> 의 경우 아래와 같은 작업이 벌어지게 되고 <table border=0 cellpadding=0 cellspacing=0 width=550> 문자열은 아래의 작업 결과로 만들어진 <table border=0 cellpadding=0 cellspacing=0 width=500> 으로 치환된다.
$1 + 500 + $3 = "<table border=0 cellpadding=0 cellspacing=0 width=" + "500" + ">"
= "<table border=0 cellpadding=0 cellspacing=0 width=500>"
주의해야 할 점은 위에서도 한 번 얘기했던 것처럼 (?:...) 과 같이 ?: 등의 메타 문자가 포함된 소괄호의 경우에는 따로 그 부분을 $n 변수에 저장하지 않는다는 것이다. 일련의 ?: 메타 문자 시리즈들은 바로 그런 목적을 위해 만들어진 것으로 그 외에도 각각 특수한 기능들을 가지고 있다. 이에 관한 추가적인 사항은 각자 알아보기 바란다.
다음은 지금까지 설명한 내용을 처리하는 실제 코드이다. 출력을 위해서 Replace() 함수를 몇 번 호출하는 부분이나 검색 대상 문자열을 만드는 부분을 빼고 나면 지금까지의 긴 설명이 무색할 정도로 간단하다.
<% String, |
정규 표현식에 관한 첫 번째 글에서 필자가 날짜 문자열을 처리하는 방법에 관해서 얘기했던 적이 있다. 이번에 그와 같은 날짜 문자열들에 대한 간단한 예를 살펴본다. '2000년 10월 13일' 을 '10/13/2000' 형식으로 치환하려면 다음과 같은 코드를 사용하면 된다.
RegExpReplace("(d{4})년 (d+)월 (d+)일", "2000년 10월 13일", "$2/$3/$1")
또한, 그 반대의 경우는 다음과 같다.
RegExpReplace("(d+)/(d+)/(d{4})", "10/13/2000", "$3년 $1월 $2일")
이 정도 수준만 되도 불편했던 프로그래밍이 한결 수월해진다. 그러나 당연한 얘기겠지만 RegExp.Replace() 메소드도 만능은 아니다. 예를 들어 위의 사례에서와 같은 경우 Width 어트리뷰트의 값을 항상 500 으로 치환해야 하는 것이 아니라, 기존의 값에 20 을 더한 값으로 치환해야 한다면 위의 정규 표현식은 사용할 수 없다.
즉, 다음과 같은 코드는 원하는 대로의 결과가 나오지 않는다는 뜻이다.
RegExpReplace("(<table.*width=(?:|'|""))([56789]d{2}|d{4})((?:|'|"").*>)", Sample_String, "$1($2+20)$3")
이는 정규 표현식이 문자열들의 모든 요소를 말 그대로 일괄적으로 문자열로 취급하기 때문인데 이런 경우에는 지난번 글에서 설명했던 RegExp.Execute() Method 와 Matches Collection, SubMatches Collection 등의 기능과 함께 VBScript 의 일반적인 함수들을 사용하여 추가적인 작업을 구성해 주어야 한다. 사실 역참조 변수 $n 은 SubMatches Collection 의 Item 들과 정확하게 일 대 일로 대응되는 개념이다.
그리고 필자의 사견으로는 VBScript 의 정규 표현식 처리는 Perl 과 같은 여타 다른 프로그래밍 언어들의 그 것에 비교하여 상당히 부족하다는 느낌이다. 물론 구버전의 VBScript 에서 정규 표현식이 지원되지조차 않았던 것을 생각한다면 지금의 상태도 매우 큰 발전이겠지만 아쉬움이 남는 것은 어쩔수 없는 것 같다.
그러나 그렇다고 해서 VBScript 에서의 정규 표현식에 투자하는 시간이 낭비라고는 생각하지 않는다. 나름대로 VBScript 의 정규 표현식은 Visual Basic.NET 의 정규 표현식 기능으로 접근해 나가는 과도기적인 역활을 훌륭히 수행할 수 있다고 생각하며 그 자체만으로도 매우 강력한 기능을 가지고 있다.
또한 정규 표현식 자체가 특정 프로그래밍 언어에 종속되는 것이 아니고 해당 언어에서 정규 표현식을 얼마나 충실하게 구현했는가에 따라 그 특성이 바뀌는 것이므로, 비교적 접근하기 쉬운 VBScript 의 정규 표현식은 아마도 다른 프로그래밍 언어들의 정규 표현식 구현에 접근하기 위한 좋은 시작점이 될 수 있지 않을까 한다.
역시 이번글도 그 분량이 필자의 예상을 휠씬 초과했다. 지난번 글에서도 얘기했지만 원래 이번글에서는 위에서 설명한 RegExp.Replace() 메소드와 함께 JavaScript 상에서의 정규 표현식을 간략하게나마 소개하고 넘어가려고 했었다. 그러나 글의 분량이 약간 어정쩡해져 버려서 애초에 의도했던 것과는 달리 이번 글은 이정도에서 마무리를 해야 할 것 같다. JavaScript 상에서의 정규 표현식에 관한 내용도 나름대로 그 분량이 매우 많기 때문에 괜한 무리를 하는 것은 바람직한 일이 아닐것 같다.
그리고 아쉽지만 다음글에서도 역시 JavaScript 상에서의 정규 표현식에 관한 내용은 다루지 않는다. 지금 진행되고 있는 일련의 글들은 모두 VBScript 5.6 의 특성에 관한 것으로 일종의 연재물적인 성격을 가지고 있다. 따라서 지금 갑자기 JavaScript 에 관한 이야기를 새로 시작하는 것은 전체적인 글의 흐름에 있어 무리를 가져올 것 같다는 생각이다.
그런 이유로 JavaScript 상에서의 정규 표현식에 관한 얘기는 VBScript 5.6 에 관한 글들이 모두 마무리된 후에야 비로소 다시 시작할 수 있을 것 같다. 다음글에서는 VBScript 5.6 의 클래스 (Class) 에 관해서 알아보고 지면이 허락된다면 이 클래스를 이용하는 Remote Scripting 에 관해서도 알아보도록 하겠다.
댓글 없음:
댓글 쓰기