간단한 Web 프로젝트를 Spring Boot로 만들었는데

이상하게 Tomcat에 War로 올리면 404 에러로 경로를 못찾는 현상이 발생했다.

 

덕분에 몇시간을 날렸는데 이유는 간단했다..

 

 

Spring Boot의 Main 클래스에 SpringBootServletInitializer를 상속받지 않아서였다.

 

일반적인 Spring Framework에서는 Web.xml에 DispatcherServlet을 등록하는 작업이 필요했다.

Servlet 3.0에서는 web.xml이 없이도 배포가 가능 해졌는데 Apache Tomcat 7부터 지원을 한다.

 

web.xml의 역할을 WebApplicationinitializer 인터페이스를 구현하여 프로그래밍으로 ServletContext를 구현할 수 있도록 바뀐것이다.

SpringBootServletInitializer는 WebApplicationinitializer의 구현체이다. 

SpringBootServletInitializer를 이용하여 WebApplicationContext를 생성하여 Servlet Context에 추가할 수 있다.

 

나 같은 경우에는 프로젝트에 web.xml도 없었고, WebApplicationinitializer를 구현한 SpringBootServletInitializer도 없었기 때문에 Tomcat에서 URL의 요청을 받아드릴수가 없었던 것이다..

 

SpringBootServletInitializer를 상속 한다는건 결국 Tomcat과 같은 Servlet Container 환경에서 Spring Boot Application을 동작 가능 하도록 Web Application Context를 구성한다는 의미이다.!

 

나와 같은 삽질은 다른 사람들은 하지 않았으면....

혼자 집에서 심심풀이로 만드는게 있는데 프론트에서 서버로 보내는 요청이 실패가 되는 현상이 발생했다.

 

크롬 콘솔을 보니 cross 어쩌고 저쩌고..

 

왜그런지 찾아보니 보안상의 이유로 스크랩트 내에서 HTTP요청을 SOP(Same-Origin Policy)로 막는 현상이 있었다.

 

SOP는 두 Origin간에 프로토콜, 포트, 호스트가 같아야 동일 Origin으로 간주하는데

 

예를 들어서 서버가 https://luji.tistory.com/ 주소를 사용중이고 Client에서 보내는 요청이 

1) https://luji.tistory.com/post  일 경우에는 성공

2) http://luji.tistory.com/post  일 경우에는 실패 (프로토콜이 다름 https / http)

3https://luji.tistory.com:8080/post  일 경우에는 실패(포트가 다름)

4https://www.luji.tistory.com/post  일 경우에는 실패(호스트가 다름)

 

이런 경우에 SOP정책에 의해 실패된다

 

그렇담 해결방법은 클라이언트에서 JSONP를 사용하거나 서버에서 CORS를 이용하여 해결 가능하다.

 

Spring Boot에서 CORS를 이용한 방법

1) Controller 개별 지정

@CrossOrigin
@GetMapping("/q")
String getTest(int test) throws Exception {
return testService.test(test);
}

 

2) 전역으로 지정

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**");
}
}

 

티스토리에 플러그인을 적용하지 않아서 코드가 더러운데 나는 WebConfig.java를 생성하여 addCorsMappings를 오버라이드하는 방식으로 전역파일로 따로 빼서 addMapping에 경로를 지정해서 사용중이다.

IntelliJ 스프링부트 그래들로 생성하기

이번에는 IntelliJ IDE에서 Spring Boot Project를 Gradle로 생성하는 방법을 설명해보겠습니다. IntelliJ가 처음이신 분들에게 도움이 되실꺼라 생각이 되면서 저도 잊어먹지 않기 위해(정말 간단하지만..) 글로 남겨 놓을려고 합니다.





1) Create New Project 클릭





2) Spring Initializr 클릭




3) Project Metadata 설정 (Type에서 Gralde Project 선택)





4) Gradle에 추가할 디펜던시 선택







5) 임포트 설정 Gradle 




6) Gradle Build 중..



끝~

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이 아님을 확인한다.


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


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에 해당된다.





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 객체의 어떠한 처리도 완결해야 할 필요가 있다는 것을 의미한다.





안녕하세요.



공기어때팀입니다. 

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

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

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




업데이트 내용


1) GPS 탐색 기능 개선 

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

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


2) Update 오류 수정

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

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



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

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

감사합니다~!


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


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


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



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



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


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


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


3. 실행 결과 확인


4. 재부팅


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





2. 클린 부팅


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


1. msconfig 실행


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


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

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


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




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


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



한글 테이블 레코드 분석




한글 문서에 생성되는 표를 바이너리값으로 파싱하는걸 포스팅해보려고한다.
기본적으로 압축이 풀린 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 


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

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




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




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

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

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


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


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

 

 +

 +

 

 

 

 

 

 

 


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

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

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





+ Recent posts