[Java/Javascript]캐리지리턴과 브라우져 비호환성

이번에 회사 업무를 하면서 간단하다고 생각했던 일에 큰 코를 다쳤던 일이 있었습니다.

바로 “캐리지리턴“과 “브라우져 비호환성” 문제였습니다.

================================================================

먼저, “캐리지리턴“은 mysql DB에 저장되어 있던 한글 varchar 데이터를 가져와서

컨트롤러의 ModelMap에 변수에 담아 자바스크립트단의 변수로 쓸 생각이었습니다.

그런데 문제는, 이 변수가 자꾸 define하는 과정에서 “Unexpected token ILLEGAL

에러를 뱉어냈습니다.

debugging을 해봐도 정상적인 데이타가 나와 한참을 삽질하던 도중…

데이타에 “캐리지 리턴“이 포함되어 있는 것을 어렵사리 확인하였습니다 ㅠㅠ

그래서 아래와 같이 서버단에서 “캐리지 리턴“을 replace 처리해서 삽질을 끝냈습니다.

  String data = data.replaceAll("\r","");

================================================================

두번째로, 서버단에서 받아온 ModelMap변수를 자바스크립트단에서 구분자로 나눠

textarea에 각 배열요소를 넣고 끝에 “엔터키”를 삽입하도록 했습니다.

<html>
<head>
<script language="javascript">
var a = [서버단 ModelMap 변수];
var arrList = a.split("|");

for(var i=0; i<arrList.length; i++) {
    if(navigator.appName == "Microsoft Internet Explorer") {  //IE
        $("#box").append(arrList[i]+"\r");
    }else{  //Crome, Mozilla, Etc..
        $("#box").append(arrList[i]+"\n");
    }
}
</script>
</head>
<body>
    <textarea id="box" name="issueHashTag" rows=20 cols=30></textarea>
</body>
</html>

위 소스에서 보이는 바와 같이, 브라우져 별로 “엔터키”를 삽입할 때도 “IE”는

캐리지리턴(“\r”)을 사용하고 그 외의 브라우져는 라인피드(“\n”)를 사용합니다.

만약, IE에서 라인피드(“\n”)를 사용했다면 현재 위치에서 아래로 한줄 내리는 기능은

동작하지만 커서위치가 한칸 띄어지는 현상을 발견할 수 있습니다.

그러므로 캐리지리턴(“\r”)을 사용해야 다음줄의 첫 위치로 커서를 옮길 수 있습니다.

================================================================

마지막으로,  위 소스에서 Textarea에 값을 넣어놓고 해당 값을 삭제하는 순간 크롬에서

아래와 같은 경고메세지가 나오더군요.

event.layerX and event.layerY are broken and deprecated in WebKit. They will be removed from the engine in the near future.

‘이건 또 모야…’ 하고 구글링을 해보니 jquery를 1.7로 업데이트 했더니 해당 문제를

고칠 수 있었다고 나와있어 그것도 시도해 보았지만 Textarea에 액션이 들어가는 순간,

저 메세지는 변함없이 발생하였습니다.

그리고 Textarea에 기존에 세팅되어 있던 값을 건드리지 않고 새로운 값을 넣었을 때

즉, 데이타를 append 할때는 들어갔지만 역시나  Textarea에 액션을 가하면 여지없이

위의 경고메세지가 출력되며 정상작동하지 않는 문제가 있었습니다.

파폭도 동일하게 정상작동하지 않는 문제가 발생했습니다

여기서 중요한건!!!!

“IE에선 이상없이 잘 동작한다는 것입니다! 오잉~~~ ㅋㅋ” 

================================================================

클라이언트 개발이 어렵다는 사실을 새삼 실감하고 공부가 많이 필요하겠구나 느낀

요 며칠이었습니다.

참고로, jquery 버전은 1.6.1과 1.7.1을 번갈아 사용했습니다.

혹시나 해결책을 알고 계신 분은 댓글 꼭 부탁드릴께요^^

*참고 url

Advertisements

[Java]정규식 사용하여 트위터 해시태그 추출기능 만들기

트위터에 보면 “#” 을 붙여 해시태그를 만듭니다.

그런데, 가만히 해시태그가 되는 규칙을 살펴보면 “#“로 시작해서

특수문자공백이 오면 “#“부터 그 사이를 해시태그로 인식해서 보여줍니다.

다만, 연속적으로 “#“을 사용하면 트위터에선 해시태그로 인식을 하지 않더군요.

예를 들면 “#테스트#테스트1#테스트2″같은 문자열은 각각

“#테스트”, “#테스트1”, “#테스트2” 세개의 해시태그로 인식되어야 할 것 같지만 결과는

아무것도 해시태그로 인식하지 못합니다. 궁금하면 직접 한번 해보세요~*

그래서 회사의 업무와 연관되기도 하여서 해시태그 추출기능 테스트코드를 만들었습니다.

@Test
public void extractHashTagTest() {
    String test ="나는 어딘가에서 #테스트 포를 #가#나다#라라라$ #배$#%@ #443##fefef";
    String test1 ="#아무개가 세미나에 참여했다.";
    String test2 ="#아무개? 이 캐릭터는 누구냐?";
    String test3 ="#작두#망토 어때요?";
    String test4= "말도안돼#니가$정말#그 사람이었다니 말야##이상하군!! 정말";

    Pattern p = Pattern.compile("\\#([0-9a-zA-Z가-힣]*)");
    Matcher m = p.matcher(test4);
    String extractHashTag = null;

    while(m.find()) {
	extractHashTag = sepcialCharacter_replace(m.group());

	if(extractHashTag != null) {
		logger.debug("최종 추출 해시태그 :: {}", extractHashTag);
	}
    }
}

public String sepcialCharacter_replace(String str) {
    str = StringUtils.replaceChars(str, "-_+=!@#$%^&*()[]{}|\\;:'\"<>,.?/~`) ","");

    if(str.length() < 1) {
   	return null;
    }

    return str;
}

test부터 test4까지 테스트를 돌려보면 아래와 같은 결과가 나옵니다.

결과를 보면 아시겠지만,  “#“연속으로 사용한 문자열도 순서적으로 추출할 수 있도록

하였습니다.  “작두“와 “망토“가 결과로 튀어나온거 보면 확인할 수 있죠~

추출된 해시태그에서 “#“는 삭제하였습니다. 순수한 문자열만 추출하기 위해서요.

그래서 한단계를 더 거치게 되는데, 만약 정규식이 최적화되어서 한번에 #을 제외한

문자열을 추출할 수 있다면 더욱 베스트겠죠.

혹시 더 좋은 방법을 알고 계시다면  댓글로 달아주세요~^^

*다른 정규식 예제 포스팅은 아래에서 확인하실 수 있습니다.

[Java]if-else 리팩토링

오늘 회사에서 업무중에 아무 생각없이 if-else 중첩으로 코딩하고 “기능적으로”  잘

돌아가는 것을 확인한 뒤, 해당 업무를 접으려고 했습니다.

그런데 문득 이런 생각을 해보았습니다.

왜 맨날 분기로직은 if-else로만 짜야할까… 과연 내가 아닌 다른 사람이

내 코드를 유지보수한다면 저걸 얼마나 이해하고 작업할 수 있을까

그래서 과감하게 if-else 로직을 걷어내고 리팩토링 하기로 결정하고 구글링을 통해 그런

사례가 있는지 찾아서 테스트코드를 만들었습니다.

아래 참고 url은 꼭 한번 읽어보시기 바랍니다.

소소코드참고 url에서 다운받아서 인터페이스를 구현한 enum class의 메서드에서

String Object를 리턴하도록 약간 수정하였습니다.

1. 먼저 기존 if-else class입니다.

public class IfThenElse {

    public String invoke(String operationName) {
    	String result = null;

        if (StringUtils.equals(operationName, "operation1")) {
            result = operation1();
        } else if (StringUtils.equals(operationName, "operation2")) {
        	result = operation2();
        } else if (StringUtils.equals(operationName, "operation3")) {
        	result = operation3();
        }

        return result;
    }

    public String operation1() {
    	return "operation_1";
    }

    public String operation2() {
    	return "operation_2";
    }

    public String operation3() {
    	return "operation_3";
    }
}

2. 두번째로 if-else 분기문을 enum 클래스로 리팩토링한 코드입니다.

public class NewIfThenElse {

    private interface IOperation {
        String apply(String name);
    }

    private enum Operation implements IOperation {

        OPERATION_1("operation1") {
            public String apply(String name) {
                return "operation_1";
            }
        },
        OPERATION_2("operation2") {
            public String apply(String name) {
            	return "operation_2";
            }
        },
        OPERATION_3("operation3") {
            public String apply(String name) {
            	return "operation_3";
            }
        };

        private static Map<String, Operation> requestLookup;

        static {
            requestLookup = new HashMap<String, Operation>(3);
            requestLookup.put(OPERATION_1.getName(), OPERATION_1);
            requestLookup.put(OPERATION_2.getName(), OPERATION_2);
            requestLookup.put(OPERATION_3.getName(), OPERATION_3);
        }

        private final String name;

        private Operation(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        @Override
        public String toString() {
            return "Operation{" + "name='" + name + '\'' + '}';
        }

        public static Operation getOperationByName(String name) {
            return requestLookup.get(name);
        }
    }

    public String invoke(String operationName) {
    	String result = null;
        Operation operation = Operation.getOperationByName(operationName);
        if (operation != null) {
            result = operation.apply(operationName);
        }

        return result;
    }
}

3. 마지막으로 저 두개의 클래스를 단위테스트하는 코드입니다.

import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * if-else Refactoring 테스트케이스
 *
 * @author bluepoet
 *
 */
public class IfElseRefactoringTest {
	Logger logger = LoggerFactory.getLogger(IfElseRefactoringTest.class);
	private String result;

	@Test
	public void procedural() {
		IfThenElse obj = new IfThenElse();

		result = obj.invoke("operation2");
		logger.debug("결과 : {}", result);
		assertThat(result, is("operation_2"));
	}

	@Test
	public void refactor() {
		NewIfThenElse obj = new NewIfThenElse();
		result = obj.invoke("operation1");

		result = obj.invoke("operation1");
		logger.debug("결과 : {}", result);
		assertThat(result, is("operation_1"));
	}

}

단위테스트 결과는 물론, 녹색막대가 뜨며 두개의 메서드 모두 통과로 나옵니다.

로그도 위와같이 console창에서 출력됩니다.

이번에 리팩토링을 진행하면서 기존의 if-else문 자체를 완벽히 거둬내지는 못했지만,

관심대상을 별도의 클래스로 분리해 독립적으로 구현하는 것에는 일부분 성공했습니다.

OOP적인 부분에서는 절반의 성공은 했다고 생각됩니다.

그리고 기존의 코드를 최대한 안 건드리려고 한 부분도 일부분은 만족스럽게 구현했구요.

이것도 예제의 일부분일 뿐이고, 아마 더 좋은 구현사례가 많이 있을 것입니다.

혹시 좋은 리팩토링 사례가 있다면 댓글 남겨주시구요.

오늘 회사 선배개발자님과 커피를 마시며 한 대화중에 떠오르는 말이 있네요

리팩토링은 새로운 기능을 개발할 때 할 수 있는게 아니라 비교적 여유가 있을 때,

기존의 코드를 고치며 하는 것이 더 바람직하다. 그리고 오히려 그렇게 리팩토링을

하는 것이 실력향상에 더 도움이 된다.

리팩토링의 길은 멀고도 험하지만, 그것을 해냈을 때 프로그래머로써 느끼는 “쾌감“은

다른 무엇과도 비교할 수 없는 달콤한 열매겠죠? ^^

*참고 url

[Java]정규식 “|(or)” 사용하기

어제 정규식예제로 포스팅했던 [Java]정규식 예제 에서 마지막 예제였던

@Test
	public void 볼드_이탤릭체_함께치환() {
		String str = "이젠 나도 _지쳤어_ 그냥 *힘차게223dfefef* 발돋움 하는거야!!";
		Pattern p = Pattern.compile("\\_([0-9a-zA-Z가-힣]*)\\_");
		Pattern p1 = Pattern.compile("\\*([0-9a-zA-Z가-힣]*)\\*");
		Matcher m = p.matcher(str);
		Matcher m1 = p1.matcher(str);

		while(m.find()) {
			str = str.replace(m.group(), getHtml(m.group(), EsCharacter.ITALIC));
		}

		while(m1.find()) {
			str = str.replace(m1.group(), getHtml(m1.group(), EsCharacter.BOLD));
		}

		logger.debug("## 볼드/이탤릭체 함께 치환한 문자열 : {}", str);
	}

위의 예제처럼, 여러개의 정규식을 별도로 선언하고 매칭해 치환하는 테스트케이스가

있었고, 수정해야될 여지가 있다고 끝맺음을 했습니다.

회사동료 정재훈씨의 도움으로 연산자 “|(or)”을 사용하여 간단히 처리하였습니다.

@Test
	public void 볼드_이탤릭체_함께치환2() {
		String str = "이젠 나도 _지쳤_어_ 그냥 *힘차게2**23dfefef* 발돋움 하는거야!!";
		Pattern p = Pattern.compile("\\_([0-9a-zA-Z가-힣]*)\\_|\\*([0-9a-zA-Z가-힣]*)\\*");
		Matcher m = p.matcher(str);
		String matchingStr  = null;

		while(m.find()) {
			logger.debug("## 일치하는 문자열 : {}", m.group());
			matchingStr = m.group();

			if(matchingStr.contains("_")) {
				str = str.replace(m.group(), getHtml(m.group(), EsCharacter.ITALIC));
			}

			if(matchingStr.contains("*")) {
				str = str.replace(m.group(), getHtml(m.group(), EsCharacter.BOLD));
			}
		}

		logger.debug("## 볼드/이탤릭체 함께 치환한 문자열2 : {}", str);
	}

	public String getHtml(String str, EsCharacter ch) {
		String result = null;

		switch (ch) {
		case BOLD:
			result = "<b>"+replaceChBlank(str,"\\*")+"</b>";
			break;
		case ITALIC:
			result = "<I>"+replaceChBlank(str,"\\_")+"</I>";
			break;
		default:
			break;
		}

		return result;
	}

	public String replaceChBlank(String str, String ch) {
		return str.replaceAll(ch, "");
	}

	public enum EsCharacter {
		BOLD, ITALIC;
	}

 

생각보다 간단하죠.

다만, 여러개의 정규식을 한번에 치환해야 할 경우에 과연 모든 것을 or로 연결해서

치환해야 하는지는 더 생각해봐야 할것 같습니다.

패턴에 대한 정규식을 따로 자료구조에 정의해서 compile 메서드의 인자로 넘기는

방법은 어떨까 싶네요.

결과적으로 똑같지만, 코드의 가독성은 훨씬 나아지지 않을까 싶습니다.

더 효율적인 해결방법을 알고 계시면 댓글 남겨주세요^^

[Java]정규식 예제

지금 오픈소스팀에서 만들고 있는 위키중 중요부분인 파서부분의 정규식 예제를 간단히

만들어보았습니다.

많은 위키구문중에 대표적으로 “볼드“와 “이탤릭체“만 테스트코드로

작성했습니다.

고민중인 것은, “볼드와 이탤릭체를 함께 치환할 수 없느냐 하는 문제“입니다.

아래 코드를 보시면,

package com.glider.test.service;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author bluepoet
 *
 */
public class GliderWikiTest {
	Logger logger = LoggerFactory.getLogger(GliderWikiTest.class);

	@Test
	public void 별표_가운데문자열_처리() {
		String str = "지금은 알 수 없어 *그대aff2*";
		Pattern pattern = Pattern.compile("\\*([0-9a-zA-Z가-힣]*)\\*");
		Matcher match = pattern.matcher(str);

		for(int i=0; match.find(); i++) {
			logger.debug("## 정규식안의 문자열 빼내기 : {}", match.group(1));
		}
	}

	@Test
	public void 별표_볼드로_치환() {
		String str = "지금은 알 수 없어 *그대aff2* 떠나는 내 진심을*fefefefefefe럴마널더22323*";
		Pattern pattern = Pattern.compile("\\*([0-9a-zA-Z가-힣]*)\\*");
		Matcher match = pattern.matcher(str);

		while(match.find()) {
			logger.debug("## 일치하는 문자열 : {}", match.group());
			str = str.replace(match.group(), getHtml(match.group(), EsCharacter.BOLD));
		}

		logger.debug("## 별표를 볼드로 치환한 문자열 : {}", str);
	}

	@Test
	public void 이탤릭체_치환() {
		String str = "이젠 나도 _지쳤어_";
		Pattern p = Pattern.compile("\\_([0-9a-zA-Z가-힣]*)\\_");
		Matcher match = p.matcher(str);

		while(match.find()) {
			logger.debug("## 일치하는 문자열 : {}", match.group());
			str = str.replace(match.group(), getHtml(match.group(), EsCharacter.ITALIC));
		}

		logger.debug("## 언더바를 이탤릭체로 치환한 문자열 : {}", str);
	}

	@Test
	public void 볼드_이탤릭체_함께치환() {
		String str = "이젠 나도 _지쳤어_ 그냥 *힘차게223dfefef* 발돋움 하는거야!!";
		Pattern p = Pattern.compile("\\_([0-9a-zA-Z가-힣]*)\\_");
		Pattern p1 = Pattern.compile("\\*([0-9a-zA-Z가-힣]*)\\*");
		Matcher m = p.matcher(str);
		Matcher m1 = p1.matcher(str);

		while(m.find()) {
			str = str.replace(m.group(), getHtml(m.group(), EsCharacter.ITALIC));
		}

		while(m1.find()) {
			str = str.replace(m1.group(), getHtml(m1.group(), EsCharacter.BOLD));
		}

		logger.debug("## 볼드/이탤릭체 함께 치환한 문자열 : {}", str);
	}

	public String getHtml(String str, EsCharacter ch) {
		String result = null;

		switch (ch) {
		case BOLD:
			result = "<b>"+replaceChBlank(str,"\\*")+"</b>";
			break;
		case ITALIC:
			result = "<I>"+replaceChBlank(str,"\\_")+"</I>";
			break;
		default:
			break;
		}

		return result;
	}

	public String replaceChBlank(String str, String ch) {
		return str.replaceAll(ch, "");
	}

	public enum EsCharacter {
		BOLD, ITALIC;
	}
}

볼드_이탤릭체_함께치환() 메소드를 보면, 각자 정규표현식을 따로 만들고 매쳐도 따로

만들어 while문을 두번 돌리고 있습니다.

실제로 파서를 만들때, 당연히 저런 방식으로는 만들면 안되겠지요.

한번 전체 구문을 돌면서 모든 위키구문을 치환하는게 바람직할 것입니다.

 

jspwiki를 좀 뜯어봤는데, 파서 부분을 파악하는것도 쉽지가 않네요 ㅎㅎ

 

*테스트*입니다. 잘 _될까요_??

 

위의 예제 문자열에서 *와 _사이의 문자열을 정규식으로 동시에 치환할 수 있는

좋은 방법 알고 계시면 댓글 부탁드릴게요^^