JUnit의 개요

- Java에서 독립된 단위테스트(Unit Test)를 지원해주는 프레임워크이다.

- Desgin 패턴과 Eclipse IDE를 개발한 Erich Gamma가 제작한 프레임워크다.

- 단정(assert) method로 테스트 케이스의 수행 결과를 판별한다.

- jUnit4부터는 테스트를 지원하는 어노테이션을 제공한다. (@Test, @Before, @After)

- 각 @Test 메서드가 호출할 때 마다 새로운 인스턴스를 생성하여 독립적인 테스트가 이루어지도록 한다.




단위테스트(Unit Test)란?

- 소스코드의 특정 모듈이 의도된 대로 정확히 작동하는지 검증하는 절차, 즉 모든 함수와 메소드에 대한 테스트 케이스(Test case)를 작성하는 절차를 말한다.

- JUnit은 보이지 않고 숨겨진 단위 테스트를 끌어내어 정형화시켜 단위테스트를 쉽게 해주는 테스트 지원 프레임워크다




JUnit Annotation

@Test

- @Test가 선언된 메서드는 테스트를 수행하는 메서드가 된다.

- JUnit은 각각의 테스트가 서로 영향을 주지 않고 독립적으로 실행됨을 원치으로 하므로 @Test 마다 객체를 생성한다.



@Ignore

- @Ignore가 선언된 메서드는 테스트를 실행하지 않게 한다.



@Before

- @Before가 선언된 메서드는 @Test 메서드가 실행되기 전에 반드시 실행되어 진다.

- @Test 메소드에서 공통으로 사용하는 코드를 @Before 메소드에 선언하여 사용하면 된다.



@After

- @After가 선언된 메서드는 @Test 메소드가 실행된 후 실행된다.



@BeforeClass

- @BeforeClass 어노테이션은 @Test 메소드보다 먼저 한번만 수행되어야 할 경우에 사용하면 된다.



@AfterClass

- @ AfterClass 어노테이션은 @Test 메소드보다 나중에 한번만 수행되어야 할 경우에 사용하면 된다.





JUint Method

method name 

description 

 assertEquals(a, b)

객체 a와 b의 값이 일치함을 확인한다. 

 assertArrayEquals(a, b)

배열 a와 b의 값이 일치함을 확인한다. 

 assertSame(a, b) 

객체 a와 b가 같은 객체임을 확인한다.

assertEquals 메서드는 두 객체의 값이 같은지 확인하고 assertSame 메서드는 두 객체의 레퍼런스가 동일한가를 확인한다. 

 assertTrue(a) 

조건 A가 참인가를 확인한다.

 assertNotNull(a) 

 객체 A가 null이 아님을 확인한다.


그 외 다양한 테스트용 메소드를 제공한다.


'JAVA > SPRING FRAMEWORK' 카테고리의 다른 글

[Spring] JUnit의 어노테이션과 메소드  (0) 2018.09.10
[Spring] IoC와 DI  (0) 2018.09.10
Posted by 루우지

IoC (Inversion of Control) 와 DI(Dependency Injection)

- "제어의 역전" 즉 인스턴스 생성부터 소멸까지의 인스턴스 생명주기 관리를 개발자가 아닌 컨테이너가 대신 해준다 라는 뜻이다.

- 컨테이너 역할을 해주는 프레임워크에게 제어하는 권한을 넘겨서 개발자의 코드가 신경 써야 할것을 줄이는 전략이다.

- IoC 컨테이너는 객체의 생성을 책임지고, 의존성을 관리한다.

- POJO의 생성, 초기화 서비스, 소멸에 대한 권한을 가진다.

- 개발자가들이 직접 POJO를 생성 할 수 있지만 컨테이너에게 맡긴다.



DL (Dependency Lookup) : 의존성 검색

 - 저장소에저장되어 있는 Bean에 접근하기 위해 컨테이너가 제공하는 API를 이용하여 Bean을 Lookup하는 것


DI (Dependency Injection) : 의존성 주입

 - 각 클래스간의 의존관계를 빈 설정정보르 바탕으로 컨테이너가 자동으로 연결 해주는 것]


DL사용시 컨테이너에 종속성이 증가하여 주로 DI를 사용한다.


DI의 유형 

1) Setter Injection : Setter 메서드를 이용한 의존성 삽입

 - 의존성을 입력 받는 setter 메서드를 마들고 이를 통해 의존성을 주입한다.

2) Constructor Injection

 - 필요한 의존성을 포함하는 클래스의 생성자를 만들고 이를 통해 의존성을 주입한다.

3) Method Injection

 - 의존성을 입력 받는 일반 메서드를 만들고 이를 통해 의존성을 주입한다.








다형성 Polymorphism = Poly(다양한) + Morphism(변형, 변신)




객체를생성할때 다형성에 의거해서 아래와 같이 생성을 하면



Printer p1 = new StringPrinter();

Printer p2 = new ConsolePrinter();

부모클래스타입  변수 = new 자식 클래스의타입();



위와 같은 형식으로 으로 코드를 작성하면 아래와 같은 뜻을 내포한다.

-> StringPrinter 클래스는 Printer 타입이다.

-> ConsolePrinter 클래스는 Printer 타입이다. 


- 이게 바로 java에서 말하는 다형성이다.

- 타입은 Printer 타입이지만 다양한 StringPrinter, ConsolePrinter 변형이 가능하다.

- 구현체는 언제든지 바뀔수 있기 때문에 인터페이스만 바라보게끔 하는 방식 








Setter Injection의 <property> tag

- Setter method를 통해 의존관계가 있는 Bean을 주입하려면 <property> tag의 ref 속성을 사용 할 수있다.

- Setter method를 통해 Bean의 레퍼런스가 아니라 단순 값을 주입하려고 할 때는 <property>tag의 value 속성을 사용한다.

ref -> Bean id를 이용하여 주입할 Bean을 찾는다.

value -> 단순 값 또는 Bean이 아닌 객체를 주입할 때 사용한다.

public class Hello {
	String name; 
	Printer printer;

	public Hello() {};

	public void setName(String name) {
		this.name = name;
	}

	public void setPrinter(Printer printer) {
		this.printer = printer;
	}
}


위의 소스를 xml로 빈설정을 해주면 아래와 같다.


<bean id="Hello" class="source.Hello">

<property name="name" values="Spring" />    // setName Method

<property name="printer" ref="printer" />       // setPrinter Method 

</bean>

<bean id="printer" class="source.StringPrinter" />


보기 쉽게 각각의 키워드가 의미하는 바를 소스와 xml에서 찾을 수 있도록 하이라이팅해봤다.








Constructor Injection의 <constructor-arg> tag

- Constructor를 통해 의존관계가 있는 Bean을 주입하려면 <constructor-arg> tag를 사용할 수 있다.

- Constructor 주입방식은 생성장의 파라미터를 이요하기 때문에 한번에 여러개의 객체를 주입할 수 있다.


public class Hello {
	String name;
	Printer printer;

	public Hello() {};

	public Hello(String name, Printer printer) {
		this.name = name;
		this.printer = printer;
	}
}

1) Constructor-arg index지정하여 파라메터를 넘겨주는 경우

<bean id="hello" class="source.Hello">

<constructor-arg index="0" value="Spring"/>

<constructor-arg index="1" ref="printer" />

</bean>


2) Constructor-arg Paramater name 지정하여 넘겨주는 경우

<bean id="hello" class="source.Hello">

<constructor-arg name="name" value="Spring"/>

<constructor-arg name="printer" ref="printer" />

</bean>








Collection Type의 값 Injection

- Spring은 List, Set, Map, Properties와 같은 Collection Framework 타입을 XML로 작성해서 프로퍼티에 주입하는 방법을 제공한다.


1) 프로퍼티가 Set과 List일 경우

<bean id="hello" class="source.Hello">

<property name="names">

<list>

<values>Spring</values>

<values>IoC</values>

<values>DI</values>

</list>

</property>

</bean>

- Set으로 설정할경우 list를 set으로 설정하면 된다.


2) Map 타입 : <map>과 <entry> tag를 사용

<bean id="hello" class="source.Hello">

<property name="names">

<list>

<entry key="Kim" values="30"/>

<entry key="Lee" values="35"/>

<entry key="Ahn" values="40"/>

</list>

</property>

</bean>








Spring DI Container의 개념

- Spring DI Container가 관리한는 객체를 빈(Bean)이라고 하고, 이 빈(Bean)들을 관리한다는 의미로 컨테이너를 빈 팩토리라고 부른다.

- 객체를 생성과 객체 사이의 런타임(run-time) 관계를 DI 관점에서 볼 때는 컨테이너를 BeanFactory라고 한다.

- Bean Factory에서 여러 가지 컨테이너 기능을 추가하여 애플리케이션 컨텍스트라고 부른다.








* POJO(Plain Old Java Object)

-> JVM으로만 동작하는 객체를 POJO라한다. 일반 Java Class 객체를 뜻한다.


* Bean 

-> 스프링이 IoC방식으로 관리ㄷ는 객체라는 뜻, 스프링이 직접생성과 제어를 담당하는 객체를 Bean이라고 부른다.


* Bean Factory 

-> 스프링의 IoC를 담당하는 핵심 컨테이너를 가리킨다.

-> Bean을 등록 생성 조회 반환하는 기능을 담당함

-> 보통 BeanFacotry를 바로 사용하지 않고 ApplicationContext를 주로 이용한다.


* ApplicationContext

-> BeanFactory를 확장한 IoC 컨테이너이다.

-> Bean을 등록, 생성, 조회, 반환 관리하는 기능은 BeanFactory와 같다.

-> Spring에서는 ApplicationContext를 BeanFactory보다 더 많이 사용된다.

-> Spring의 각종 부가 서비스를 추가로 제공한다.

-> Spring이 제공하는 ApplicationContext 구현 클래스가 여러가지 종류가 제공된다. 


* Configuration Metadata

-> ApplicationContext 또는 BeanFactory가 IoC를 적용하기위해 사용하는 메타정보를 말한다.

-> 설정 메타정보는 IoC컨테이너에 의해 관리되는 Bean 객체를 생성하고 구성할 때 상용된다.

-> xml로 작성한 bean정보가 metadata에 해당된다.





'JAVA > SPRING FRAMEWORK' 카테고리의 다른 글

[Spring] JUnit의 어노테이션과 메소드  (0) 2018.09.10
[Spring] IoC와 DI  (0) 2018.09.10
Posted by 루우지

1. JDBC를 이한 DB 접근 3단계


1) 연결하기 -> Connection 생성


2) 질문하기 -> PreparedStatement 생성 후 executeQuery()하기


3) 답변받기 -> ResultSet 이용


개인적으로 생각하는 db 접근 방법이다. 



우선 JDBC를 이용한 DB접근을 하기위해서는 JDBC DRIVER가  필요하다. Oracle, MySQL, MsSQL 등 대부분의 디비에서 JDBC 드라이버를 제공해주기 때문에 알맞는 드라이버를 설치해서 Eclipse에 추가하도록 하자.



public class TestMain { public static void main(String[] args) { String serverURL = "jdbc:mysql://localhost:3306/sys"; // 주소:포트/db명 String id = "root"; // 계정명 String pw = "1234"; // 비밀번호 try { Class.forName("com.mysql.jdbc.Driver"); Connection con = DriverManager.getConnection(serverURL, id, pw); String sql = "insert into emp(empno, ename, age, deptno, mgr) values(1, '오라클', 22, 0423, 05)"; PreparedStatement pstmt = con.prepareStatement(sql); pstmt.executeUpdate(); } catch (ClassNotFoundException e) { System.out.println("드라이버가 존재하지 않습니다"); } catch (SQLException e) { e.printStackTrace(); } } }


위의 소스는 Mysql에 접속하기위한 커넥션 객체를 생성하고 Insert 문을 실행시키는 소스이다.  여기서 눈여겨 봐야할 부분은


커넥션 생성 부분, SQL 생성후 질의하는 부분 정도로 나눠볼수 있다.


Class.forName("com.mysql.jdbc.Driver");

임포트시킨 mysql connecter.jar 파일안에 있는 드라이버를클래스를 동적으로 생성후


Connection con = DriverManager.getConnection(serverURL, id, pw);


드라이버 매니저 객채를 통해 커넥션을 만든다. 이때 mysqlURL과, 계정, 비밀번호 정보를 파라메터로 넘겨준다.


String sql = "insert into emp(empno, ename, age, deptno, mgr) values(1, '오라클', 22, 0423, 05)";


그후 수행할 SQL문을 sql 변수에 입력 후


PreparedStatement pstmt = con.prepareStatement(sql);
pstmt.executeUpdate();


PrepareStement 객체를 sql문을 넣어 객체를 생성한다.

pstmt.executeUpdate() 메소드를 실행시켜 sql문을 실행한다.




2. Statement


1) Statement 

 - Statement객체는 매개변수없는 간단한 SQL문을 질의할때 사용된다. SQL문을 실행하고 결과값을 받는데 필요한 기본적인 메소드를 제공한다.


2) PreparedStatement  (Statement 상속)

 - StatementStatement 객체는 IN 메개변수를 가지거나 가지지 않은 프리컴파일된 SQL문을 실행하는데 사용되고, IN 매개변수들을 다루기 위한 메소드들이 있다.


3) CallableStatement (PreparedStatement 상속)

 - 데이터베이스 저장 프로시저의 호출을 실행하는데 사용되며, out 매개변수들을 다루기 위한  메소드들이 있따.





3. 쿼리에 따른 실행 메소드


1) execute()

      : SQL문이 여러개의 ResultSet객체나 여러개의 갱신카운트 또는 ResultSet객체들과 갱신 카운트들의 결합을 리턴하는 것이 가능할때 사용한다.


2) executeQuery()

     - 하나의 ResultSet을 만드는 SQL문에서 사용(executeQyery 메소드는 ResultSet 객체를 리턴한다.)

     - 주로 SELECT문을 이용하는 조회에서 사용됨.


3) executeUpdate()

     -  INSERT, UPDATE, DELETE 등 (DML), CREATE, DROP 등(DDL)문들을 실행하는데 사용

 


실행문을 위한 모든 메소드들은 하나가 오픈되어있다면, 호출된 Statement 객체의 현재 ResultSet을 닫아야한다.

이것은 Statement 객체를 다시 실행기전에 현재 ResultSet 객체의 어떠한 처리도 완결해야 할 필요가 있다는 것을 의미한다.

Posted by 루우지





안녕하세요.



공기어때팀입니다. 

5월 1일에 공기어때 버전업이 있었습니다. 

현재 최신 버전은 1.0.3 v 입니다.

꼭 마켓에서 업데이트를 받아주세요!




업데이트 내용


1) GPS 탐색 기능 개선 

- 기존 GPS 탐색 소스를 새롭게 작성하여 GPS탐색 소요 시간을 단축시켰습니다.

- GPS 탐색 시간 감소로 로딩시간이 줄어들었으니 좀더 쾌적하게 이용하실수 있습니다.


2) Update 오류 수정

- 기존 마켓 버전 체크하는 방식이 변경되어 실행시 업데이트 다이얼로그가 출력되는 이슈가 있었습니다.

- 이젠 업데이트 체크 방식을 바꿔 해당 버그를 수정했습니다.



이상 업데이트 내용에 대한 소개를 마치겠습니다.

미세먼지가 심한 요즘 외출시 꼭 마스크를 착용하셔서 건강 챙기시길 바랍니다.

감사합니다~!

Posted by 루우지


OS 재설치후 윈도우즈 업데이트를 받을려는데 이게 진행이 안되는 현상을 겪었습니다.


재부팅도 해보고 이것저것 해봤는데도 안되기에 구글에 검색해보니 윈도우즈 업데이트 문제 해결사를 다운로드 받으라고 하더군요.


해결사가 진단한 내용은 '잠재적인 Windows 업데이트 데이터베이스 오류가 검색됨' 이였습니다.



1. 업데이트  데이터베이스 오류 해결



그래서 직접 고쳐보자 해서 구글링해보니 MSDN에 이렇게 답변이 되어있네요.


1. CMD 우클릭 관리자 권환으로 실행


2. DISM.exe /Online /Cleanuup-image /Scanhealth 입력


3. 실행 결과 확인


4. 재부팅


사실 저는 이렇게해서도 자동으로 윈도우즈가 업데이트를 하지 못했습니다.





2. 클린 부팅


그래서 또 구글링을 했는데 그 방법은 클린 부팅을 함으로써 외부프로그램이 윈도우즈 업데이트에 간섭을 못하도록 함이였는데요.


1. msconfig 실행


2. 서비스 -> 모든  microsoft 서비스 숨기기 -> 모두 사용안함


3. 시작프로그램 -> 작업 관리자 열기 -> 시작프로그램 - >  의심되는 프로그램을 사용안함시키거나 모두 사용안함시키세요

(저같은경우 안랩의 보안프로그램이 문제였던거같습니다.)


4. 재시작 후 윈도우즈 업데이트 확인




저같은 경우에는 백신프로그램이 영향을 주고 있엇던거 같습니다.


실행중인 백신프로그램을 전부 사용안함시킨 다음 클린부팅 시키니 정상적으로 업데이트가 되네요 ^^!



Posted by 루우지

한글 테이블 레코드 분석




한글 문서에 생성되는 표를 바이너리값으로 파싱하는걸 포스팅해보려고한다.
기본적으로 압축이 풀린 Section Stream을 바이너리 뷰어로 읽었다는 가정하에 시작한다.


구분 

2018(예상) 

2017 

2016 

2015 

Orange 7.0 

20 

Orange Ade 

Trusted Orange 

총액 

28 

11 

10 


위와 같은 표가 한글파일로 생성을 했을때 만들어지는 본문의 문단레코드 바이너리는 아래와 같다.






42 00 60 01 문단 헤더

09 00 00 00 00 08 00 00 0E 00 00 00 01 00 00 00 01 00 00 00 00 00 


43 04 20 01 문단의 텍스트

0B 00 20 6C 62 74 00 00 00 00 00 00 00 00 0B 00 0D 00 


44 04 80 00 문단의 글자 모양

00 00 00 00 01 00 00 00 


45 04 40 02 문단의 레이아웃

00 00 00 00 92 3B 00 00 76 1D 00 00 76 1D 00 00 0B 19 00 00 58 02 00 00 00 00 00 00 18 A6 00 00 00 00 06 00 


47 04 C0 02 컨트롤 헤더

20 6C 62 74 11 23 2A 08 00 00 00 00 00 00 00 00 14 A6 00 00 40 1B 00 00 01 00 00 00 1B 01 1B 01 1B 01 1B 01 EB 0D F7 67 00 00 00 00 


4D 08 00 02 표 개체

06 00 00 04 05 00 05 00 00 00 FE 01 FE 01 8D 00 8D 00 05 00 05 00 05 00 05 00 05 00 02 00 00 00 





---------- 여기서부터는 각 셀의 텍스트 정보를 담는 문단 리스트 ( 셀의 갯수만큼 반복됨 ) ---------- 


48 08 E0 02 리스트 헤더 

01 00 00 00 20 00 00 04 00 00 00 00 01 00 01 00 FC 22 00 00 38 07 00 00 FE 01 FE 01 8D 00 8D 00 02 00 FC 22 00 00 00 00 00 00 00 00 00 00 


42 08 60 01 

03 00 00 80 00 00 00 00 0F 00 00 00 01 00 00 00 01 00 00 00 00 00 


43 0C 60 00 

6C AD 84 BD 0D 00 44 0C 80 00 00 00 00 00 01 00 00 00 


45 0C 40 02

00 00 00 00 00 00 00 00 E8 03 00 00 E8 03 00 00 52 03 00 00 58 02 00 00 00 00 00 00 00 1F 00 00 00 00 06 00 


---------------------- 반복 ----------------------






표 개체를 알아보기전에 조금더 위의 바이너리 형태를 구조화 시켜보면

  1. HWPTAG_PARA_HEADER
  2. HWPTAG_PARA_TEXT
  3. HWPTAG_PARA_CHAR_SHAPE
  4. HWPTAG_PARA_LINE_SEG
  5. HWPTAG_CTRL_HEADER
  6. HWPTAG_TABLE
  7. HWPTAG_LIST_HEADER         ( 셀의 수만큼 반복 )
    1. HWPTAG_PARA_HEADER
    2. HWPTAG_PARA_TEXT
    3. HWPTAG_PARA_CHAR_SHAPE
    4. HWPTAG_PARA_LINE_SEG











이번 포스팅에서 알아볼 내용은 표개체 레코드이다.

표개체 레코드는 4D 08 00 02의 바이너리 값을 갖는데

이중 4D는 TAG_ID 값, 02은 LEVEL, 00 02는 해당 레코드의 데이타 사이즈값이다.

이건 눈대중으로 대충 값을 본거이니 제대로 확인을 하고싶다면 bit단위로 끊어서 파싱하길 바란다.(지금은 눈 대중으로 바이트 단위로 파싱)





자료형 

길이 

설명 

 BYTE stream

 N

 개체 공통 속성 

 BYTE stream

 N2 

 표 개체 속성 

 BYTE stream 

 N3 

 셀 리스트 

 셀 Size * 셀 개수

 전체 길이

가변 

N + N2 + N3 

 


위의 표는 표 개체가 문단에 쓰여질때 갖는 형태이다.


N   = HWPTAG_CTRL_HEADER

N2 =  HWPTAG_TABLE

N3 =  HWPTAG_LIST_HEADER


그러면 컨트롤 헤더부터 뜯어보도록 하자.











47 04 C0 02 컨트롤 헤더

20 6C 62 74 11 23 2A 08 00 00 00 00 00 00 00 00 14 A6 00 00 40 1B 00 00 01 00 00 00 1B 01 1B 01 1B 01 1B 01 EB 0D F7 67 00 00 00 00 


HEADER

TAG_ID = 47

LEVEL = 2

SIZE = 2C(44)


DATA

20 6C 62 74

- 컨트롤 ID 'T', 'B', 'L', ' '의 값을 갖고 있다. (4 BYTE)

11 23 2A 08 00 00 00 00 00 00 00 00 14 A6 00 00 40 1B 00 00 01 00 00 00 1B 01 1B 01 1B 01 1B 01 EB 0D F7 67 00 00 00 00 

- 개체 공통 속성 값이다.



한글 문서에 이 컨트롤 헤더의 용도는 Extended type 의 컨트롤 종류를 나타내는 식별기호 32비트 id가 사용된다고 나와있다.

컨트롤 코드가 큰 범주를 나타내는 식별기호라고 한다면 컨트롤  id는 세부 분류를 나타내는 식별 기호인 셈이다.

예를 들어 단 정의 컨트롤 id는 MAKE_4CHID('c', 'o',' l', 'd')와 같은 형식으로 정의된다.


자 이쯤되면 컨트롤 헤더가 어떠한 용도로 사용되는지 이해는 된거 같으니 다음 레코드인 표 개체(HWPTAG_TABLE) 레코드를 분석 해보자.














4D 08 00 02 표 개체

06 00 00 04 05 00 05 00 00 00 FE 01 FE 01 8D 00 8D 00 05 00 05 00 05 00 05 00 05 00 02 00 00 00 


자료형 

길이 

설명 

UINT32

2

속성 

UINT16

2

줄의 갯수 

UINT16

2

칸의 갯수

HWPUINT16

2

셀 스페이싱 

BYTE stream

8

안쪽 여백 정보 

BYTE satream

2*n

Row  

UINT16

2

Border Fill ID 

UINT16

2

Valid Zone Info Size 

BYTE satream

2

영역 속성 

 전체 길이

가변 

22 + (2*row) + (10*zone) 

( 표 개체 속성 )



HEADER

TAG_ID = 4D

LEVEL = 3

SIZE = 20(32)


DATA

06 00 00 04 = 속성 값 (67108870)

05 00  = 줄의 갯수

05 00  = 칸의 갯수

00 00  = 셀스페이싱 0

FE 01  = 안쪽 여백 정보 왼쪽 510

FE 01  = 오른쪽 510

8D 00 = 위쪽 141

8D 00 = 아래쪽 141

05 00 = RowSize

05 00 = RowSize

05 00 = RowSize

05 00 = RowSize

05 00 = RowSize

02 00 = Border Fill (채우기 정보)

00 00 = ZoneInfo


HWPTAG_TABLE 레코드의 정보를 뜯어보면 해당 테이블에 대한 정보가 저장되어있는것을 확인 할 수 있다.

바이너리 값을 보았을때 위의 테이블 정보는 5행 5열의 테이블이며 표의 여백정보를 확인 할 수있다.

그렇다면 셀안에 들어가는 정보는 어디에 저장되는것일까?

위에 표에서도 설명되었지만 다음 레코드에서 셀의 정보가 저장된다.












48 08 E0 02 리스트 헤더 

01 00 00 00 20 00 00 04 00 00 00 00 01 00 01 00 FC 22 00 00 38 07 00 00 FE 01 FE 01 8D 00 8D 00 02 00 FC 22 00 00 00 00 00 00 00 00 00 00 


 자료형

길이 

설명 

BYTE stream 

문단 리스트 헤더 

BYTE stream

26 

셀 속성 

전체 길이

가변 

26+n byte 

( 셀 리스트 )




 자료형

길이 

설명 

 INT16

문단 수 

 UINT32

속성 ( 자세한건 한글 도큐먼트 참조 )

 전체 길이

 

( 문단 리스트 헤더 )

 



자료형 

길이 

설명 

 UINT16

 2

셀 주소 (COL) 

 UINT16

 2

셀 주소 (ROW) 

 UINT16

 2

열의 병합 개수 

 UINT16

 2

행의 병합 개수 

 HWPUINT

 4

셀의 폭 

 HWPUINT

 4

셀의 높이 

 HWPUINT16 array[4]

 2*4

셀4방향 여백 

 UINT16

 2

테두리/배경 아이디 

 전체 길이

26

 

( 셀 속성 )


HEADER

TAG_ID = 48 

LEVEL = 3

SIZE = 40(64)


DATA

01 00 00 00 = 문단 수

20 00 00 04 = 속성

00 00  = 셀 주소 열

00 00  = 셀 주소 행

01 00  = 열의 병합 개수

01 00  = 행의 병합 개수

FC 22 00 00   =  셀의 폭 WIDTH

38 07 00 00   =  셀의 높이 HEIGHT

FE 01  = 왼쪽 마진

FE 01  = 오른쪽 마진 

8D 00  = 위쪽 마진

8D 00  = 아래쪽 마진

02 00  = 채우기 아이디

FC 22  = 필드 이름

00 00 00 00 00 00 00 00 00 00  = 알려지지 않은 바이트 



위와 같이 분석이 되는데 셋팅해야할 값이 너무나 많다.

0행 0열의 셀의 정보를 뜯어본 값이다. 


이후로는 문단의 정보가 들어가게 되는데 해당 셀의 텍스트 문단에 대한 정보이다.


42 08 60 01 

03 00 00 80 00 00 00 00 0F 00 00 00 01 00 00 00 01 00 00 00 00 00 


43 0C 60 00 

6C AD 84 BD 0D 00 44 0C 80 00 00 00 00 00 01 00 00 00 


45 0C 40 02

00 00 00 00 00 00 00 00 E8 03 00 00 E8 03 00 00 52 03 00 00 58 02 00 00 00 00 00 00 00 1F 00 00 00 00 06 00 


위의 레코드들에 대한 설명은 생략하겠다.

이렇게 셀리스트가 다 끝날때까지 반복이 된다.




Posted by 루우지

섹션 스트림 분석하기 빈문서 분석




섹션 스트림은 바디스토리지 내에 존재하는 스트림이다.

기본적으로 레코드 형식이며, 압축이 되기도 한다.

이번 포스팅에서는 빈 도큐먼트를 파싱하는 과정을 담아보려고 한다.


섹션 스트림을 분석하기 위해서는 한글과 컴퓨터에서 제공하는 도큐먼트를 먼저 정독해야한다.


TagID 

SIZE 

LEVEL 

설명

 HWPTAG_PARA_HEADER

22 

 0 

문단 헤더 

 HWPTAG_PARA_TEXT

가변

 1 

text 정보

 HWPTAG_PARA_CHAR_SHAPE

가변

 1 

글자 모양 

 HWPTAG_PARA_LINE_SEG

가변

1

글자 레이아웃 

 HWPTAG_PARA_RANGE_TAG

가변

1

영역 태그 

 HWPTAG_CTRL_HEADER

 1 

 컨트롤 헤더

 HWPTAG_LIST_HEADER

 2 

 문단 리스트 헤더

 HWPTAG_PAGE_DEF 

40 

 2 

 용지 설정

 HWPTAG_FOOTNOTE_SHAPE

30 

 2 

각주/미주 모양 

 HWPTAG_PAGE_BORDER_FILL 

14 

 2 

쪽 테두리/배경 

 HWPTAG_SHAPE_COMPONENT 

 2 

개체 

 HWPTAG_TABLE 

가변

 2 

 표 개체

 ... (기타 그리기 개채)

가변

 

 

 HWPTAG_CTRL_DATA

가변

3

 

 ... (기타 레코드) 

 

 

 

(필자는 초반에 나오는 이 표를 보지 못해서 각각의 레코드들을 전부 읽어 레벨을 기록했다. -_-;)



위의 표는 Section Stream에서 본문 정보를 담고있는 레코드들을 추려서 표로 정리해본것이다.


포스팅을 하기 앞서 문단이란 무엇인지 알고 가야한다.

한글문서에서의 문단은 '엔터'로 기준을 나눌 수 있다.


첫번째줄 두번째줄이 엔터로 나뉘어서 작성이 되면 2개의 문단으로 인식한다.

한줄에 엔터없이 2번째줄까지 작성되어 내려가게 되면 그건 1개의 문단이다.


섹션의 첫 문단에는 구역 정의 레코드들이 들어가기 때문에 중간 중간의 문단 레코드들보다 필요한 사이즈가 크다. 

첫번째 문단 정보에는 ~ 레코드들이 들어간다.

그다음 문단 부터는 헤더, 텍스트, 글자모양, 글자 위치 정보의 레코드들이 하나의 문단 정보를 이루어 작성된다.



첫번째 문단

 

 두번재 문단

 

 3번째 문단

 

 header + text + shape + line + range_sahpe+ ctrl header + footnote+ border fill + etc

 

 HEADER

 TEXT

 CHAR_SHAPE

 LINE_SEG

 

 HEADER

 TEXT

 CHAR_SHAPE

 LINE_SEG

 

 +

 +

 

 

 

 

 

 

 


위에 표는 스트림에 작성되는 텍스트 헤더들을 표로 정리해본것이다.

첫번재 문단 정보에는 기본적인 텍스트( 테이블도 될수 있음) 정보들이 들어가며 추가적으로 각주/미주 정보라던지, 용지 설정 정보, 각주/미주 모양의 정보와 같은 부수적인 레코드들이 같이 붙는 형태이다.

두번째 문단 부터는 해당 문단에 작성된 텍스트 정보만 작성이 된다. 비교적 첫번째 문단에 비해 담기는 정보가 작다.





Posted by 루우지


이번 포스팅에서는 한글문서가 어떤형식으로 파일구조를 갖고 있고 

어떻게 바이트단위로 박혀있는지 확인 해보는 시간을 갖기 위해 글을 써보았다.



한글 문서는 Compound File (OLE2) 구조를 갖는다.

스토리지와 스트림 형태를 띄고 있는데, 스토리지는 폴더, 스트림은 파일이라고 이해하면 쉽다.

Root 밑에는 여러개의 스토리지와 스트림이 존재한다. 

아래의 표는 hwp 파일이 갖고있는 구조를 표로 표현한것이다.



 

 이름

길이 

레코드 

압축 / 압축화 

 파일 인식 정보

FileHeader (Stream)

고정 

 

 

 문서 정보

DocInfo  (Stream)

고정 

true 

true 

본문 

BodyText (Storage)

- Section0   (Stream)

가변 

true 

true 

문서요약 

\005HwpSummaryInformation  (Stream) 

고정 


 

바이너리 데이타 

BinData  (Storage)

- BinaryData0   (Stream)

- ...   (Stream)

가변 

 

true 

미리보기 텍스트 

PrvText  (Stream)

고정 

 

 

미리보기 이미지 

PrvImage  (Stream)

가변 

 

 

문서 옵션 

DocOptioin  (Storage)

- _LnkDoc  (Stream)

- DrmLicense  (Stream)

- ...

가변 

 

 

스트립트 

Script  (Storage)

- DefaultScript  (Stream)

- JScriptVersion  (Stream)

- ...

가변 

 

 

 XML 템플릿

XML Template (Storage)

- Schema   (Stream)

- Instance  (Stream)

- ...

가변 

 

 

문서 이력 관리 

DocHistroy  (Storage)

- VersionLog0  (Stream)

- VersionLog1  (Stream)

- ...

가변 

true 

 

true 



(ssviewer로 본 한글파일 내부의 구조)

각 스토리지와 스트림의 정보는 한글과 컴퓨터에서 제공하는 문서를 참고하면 이해하기 쉽다.



간단하게 파일 인식 정보를 갖고있는 FileHeaer 스트림을 뜯어보면 아래와 같은 구조를 갖고 있다.




FileHeaer은 256바이트의 고정된 길이를 갖는 스트림이다.

사진에 첨부한 FileHeader 스트림의 Binary값을 확인해보면 

초기 32바이트에는 한글 문서 형식을 알리는 시그니처 문구가 박혀있는것을 확인할 수 있다.


그다음 4바이트 크기로 한글 문서의 버전을 확인 할 수 있다. 

바이너리 뷰어로 해당 스트림을 열어보면 5.0.3.0 버전이 찍힌걸 볼 수있다.


그다음 4바이트는 파일헤더의 속성을 갖는 비트들이다. 0비트부터 11비트는 각각의 문서 속성 정보를 갖고있다.

그 이후 나머지 216바이트는 예약 바이트들로 0의 값으로 초기화되어 있는 형태를 확인할 수 있다.




한글 문서중 제일 파악하기가 쉬운게 파일헤더 스트림이다.

고정 길이를 갖고 있기에 256바이트 중에 

처음 32바이트는 시그니처, 

그다음 4바이트는 파일의 버전, 

그다음 4비트는 속성 정보,

나머지 216바이트는 예약비트로 파싱할때 비교적 수월하다.

암호화도 안되있기 때문에 그냥 가져와 바이트단위로 짜르면 한글문서의 정보를 가져올 수 있다.





다른 스트림도 이러면 좋겠지만 가변적인 길이를 갖는 스트림도 많고 압축/암호화도 걸린게 많기 때문에 

다른 스트림을 파싱하거나 렌더링할때에는 쉽게 되지는 않을꺼같다.




몇몇 스트림들은 레코드형식(헤더와 데이터의 결합)으로 구성되어있기도 하며, 

MSDN의 프로퍼티 셋 형식을 취한 스트림도 있다.




대표적인 예로 \005HwpSummaryInformation 스트림이 MSDN의 프로퍼티셋 형식을 갖고 있다.

Summary Information에 대한 자세한 설명은 MSDN을 참고

The Summary Information Property Set

The DocumentSummaryInformation and UserDefined Property Set

(한글 공식 문서의 설명)


SumamryInformation 은 MSDN의 프로퍼티셋 형식을 따른다.

해당 정보는 아래의 URL에 설명되어있다.


https://msdn.microsoft.com/ko-kr/library/windows/desktop/aa380376%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396


HwpSummaryInformation 정보를 가져오기 위해서는 바이트 단위로 짜르는것 보다는

Apache POI API에서 제공하는 프로퍼티 ID단위로 값을 가져오면 된다.

이 부분은 추 후 포스팅을 통해 값을 가져와보도록 하겠다.









마지막으로 설명하는 내용은 레코드 형식의 데이터다.


(한글의 데이터 레코드 구조)


논리적으로 연관된 데이터들을 헤더 정보와 함께 저장하는 방식을 데이터 레코드라고 한다.

레코드 구조를 가지는 스트림은 연속된 여러 개의 레코드로 구성된다. 데이터 레코드는 헤더와 데이터로 구성되며 각 헤더 정보를 활용하여 전체 논리적 구조를 생성하게 된다.

레코드의 헤더에는 데이터 확장에 대비한 정보를 가지고 있다. 따라서 이후에 ᄒᆞᆫ글의 기능이 확장되어 레코드에 데이터가 추가되는 경우에도 하위 버전의 ᄒᆞᆫ글이 상위 버전의 ᄒᆞᆫ글 문서를 읽을 수 있도록 하위 호환성이 보장된다.



Tag ID : 레코드가 나타내는 데이터의 종류를 나타내는 태그이다. Tag ID에는 10 비트가 사용되므로 0x000 - 0x3FF까지 가능하다.

0x000 - 0x00F = 일반 레코드 태그가 아닌 특별한 용도로 사용한다.

0x010 - 0x1FF = 글에 의해 내부용으로 예약된 영역(HWPTAG_BEGIN = 0x010)

0x200 - 0x3FF = 외부 어플리케이션이 사용할 수 있는 영역


Level : 대부분 하나의 오브젝트는 여러 개의 레코드로 구성되는 것이 일반적이기 때문에 하나의 레코드가 아닌 "논리적으로 연관된 연속된 레코드"라는 개념이 필요하다. 레벨은 이와 같이 연관된 레코드의 논리적인 묶음을 표현하기 위한 정보이다. 스트림을 구성하는 모든 레코드는 계층 구조로 표현할 수 있는데, 레벨은 바로 이 계층 구조에서의 depth를 나타낸다.


Size : 데이터 영역의 길이를 바이트 단위로 나타낸다. 12개의 비트가 모두 1일 때는 데이터 영역의 길이가 4095 바이트 이상인 경우로, 이때는 레코드 헤더에 연이어 길이를 나타내는 DWORD가 추가된다. , 4095 바이트 이상의 데이터일 때 레코드는 다음과 같이 표현된다.

(한글 문서파일형식에서 발췌)



대표적으로 DocInfo스트림과 Body 스토리지 내부의 Section 스트림에서 데이터 레코드형식을 취하고 있다.

TagID의 0x010에 HWP TAG 아이디를 넣음으로써 해당 레코드가 어떤 데이터를 갖고 있는지 구분할 수 있다.


현제 필자는 HwpLib 라이브러리를 통해 한글문서를 파싱, 렌더링 하는 부분을 분석중이다.

바이너리 데이터를 읽고 쓰는것이 익숙하지 않아 하루에도 머리가 여러번 쪼개지는 고통을 받고 있다.ㅋㅋ ㅜㅜ

빨리 이 과제를 마무리 짓고 싶다. ㅜㅜ




















Posted by 루우지

회사 수습 연구원으로 진행하게될 과제이다.

Java로 .hwp파일을 파싱 후 렌더링하기까지가 숙제(과제)이다. 


처음에는 low레벨까지 내려가 binary형태에서 부터 파싱을 해야 하나 싶어서 걱정이 많았는데

자비로우신 팀장님께서 결과만 나오면 일단 된다고 하셨기에 갓구글님께 도움을 요청했다.


http://www.hancom.com/etc/hwpDownload.do 

(한글 문서 파일 구조 5.0 도큐먼트)


처음에 70페이지 가까이된 한글 문서 파일 구조를 던져주실때 식겁했는데

다행스럽게도 


https://github.com/neolord0/hwplib

누군가가 만들어주신 라이브러리가 존재했다.


한글과 컴퓨터(한컴)에서 만든 워드프로세서 "한글"의 파일에 대한 라이브러리입니다.

본 라이브러리는 JAVA로 구현되었으며, 한글 파일의 하부 구조인 Microsoft Compound File의 부분은 Apache-POI의 POIFS File System을 사용하여 처리합니다. 본 라이브러리는 한글과컴퓨터의 한글 문서 파일(.hwp) 공개 문서를 참고하여 개발하였습니다. 한컴에서 제공하는 문서(HWP 5.0)는 아래URL에서 받을 수 있습니다.

http://www.hancom.com/etc/hwpDownload.do?gnb0=269&gnb1=271&gnb0=101&gnb1=140



github에 올라온 프로젝트 소개글이다.

처음에 소스 분석을 하겠다고 처음부터 쭉 따라갔는데 왠 아파치 poi 라이브러리가 나오길래 막막했었다.

알고보니 아파치 poi 라이브러니는 마이크로소프트사의 워드 포맷형식을 지원하는 라이브러리였다.


여기서 생긴 의문이 

그렇다면 한글의 .hwp파일은 MicroSoft의 word 형식이랑 같다는것인가?

hwp나 word는 같은 Compound File Binary Format 이였던것이다.

한글은 XML기반으로도 파일이 제공된다고 팀장님이 알려주셨다.

CFBF, OLE라고도 불리는 이 형태는 하단의 블로그에서 자세하게 설명해준다.

http://www.reversenote.info/ole-file-format/



이제 위의 라이브러리를 통해 1차 목표인 한글파일 읽어들여

콘솔창에 띄우는 결과물을 만들어낼 생각이다.

이미 만들어져있기에 거기까진 어렵지 않지만 그냥 가져다가만 쓰면 개인과제에 의미가 없어보여서

최대한 라이브러리를 뜯어보아서 어떤형식으로 .hwp파일을 파싱하는지 공부해보려고 한다.








Posted by 루우지

주어진 리스트가 정렬이 되어있다는 가정하에서는 

이진 검색(binary search)를 사용하는게 매우 효율적인 방법이란다.


소스를 보니 재귀호출을 사용하여 검색이 진행된다.

(JAVA 프로그래밍 면접 이렇게 준비한다 - 73P)



public class BinarySearch {

public static boolean binarySearch(final List<Integer> numbers, Integer value) {

if(numbers==null || numbers.isEmpty()) // 전달받은 numbers 리스트가 null이거나 비워져있는지 확인

return false;

Integer comparsion = numbers.get(numbers.size() / 2); //  리스트 크기 반띵하여 원소 위치의 값을 추출

if(value.equals(comparsion)) {   

return true;

}

if(value<comparsion) {

return binarySearch(numbers.subList(0, numbers.size() / 2), value);

} else {

return binarySearch(numbers.subList(numbers.size() / 2 + 1, numbers.size()), value);

}

}

public static void main(String[] args) {

List<Integer> list = new ArrayList<>();

list.add(1);

list.add(4);

list.add(5);

list.add(9);

list.add(14); // 정렬하기 귀찮아서 삽입할때 순서대로 삽입시켰다.



System.out.println(binarySearch(list, 9)); // 삽입된 리스트와, 찾고하자는 밸류값을 파라메터로 던진다.

}

}


소스가 어렵지 않아 쉽게 이해할 수 있었다.



Posted by 루우지


티스토리 툴바