XML DOM이라는 것은 XML 파일을 HTML로 불러들이는 것이다. (원래 엄밀히 말하면 그런 건 아니지만, 수행평가를 위해서 복잡하게 알 필요는 없어서 자세한 설명은 생략한다)
1학기 때는 Data Binding을 이용했으나, 이번에는 XML DOM을 이용해 보는 것이다. 뭐, 그게 그거다..
일단 대충 XML 파일을 만들어본다.
<?xml version='1.0' encoding='euc-kr'?>
<booklist>
<book kind='수필'>
<title>멋진남자 히라이켄</title>
<author>정민호</author>
<publisher>햄터리 출판사</publisher>
<price>15000</price>
</book>
<book kind='시'>
<title>홀롤로</title>
<author>이호</author>
<publisher>호이 출판사</publisher>
<price>3000</price>
</book>
<book kind='생활서'>
<title>대출의 정석</title>
<author>강대한</author>
<publisher>리드코프</publisher>
<price>50000</price>
</book>
</booklist>
<booklist>
<book kind='수필'>
<title>멋진남자 히라이켄</title>
<author>정민호</author>
<publisher>햄터리 출판사</publisher>
<price>15000</price>
</book>
<book kind='시'>
<title>홀롤로</title>
<author>이호</author>
<publisher>호이 출판사</publisher>
<price>3000</price>
</book>
<book kind='생활서'>
<title>대출의 정석</title>
<author>강대한</author>
<publisher>리드코프</publisher>
<price>50000</price>
</book>
</booklist>
이제는 Javascript를 이용하여 이 것을 HTML 파일에 띄워 볼 것이다.
여기서 Java의 객체 생성 기능을 새록새록 떠올려본다. Javascript는 Java라는 말이 들어있다 보니 Java스러운 부분이 있다.
<script language='JavaScript'>
var xmlDoc;
function btnOpen_Click()
{
xmlDoc = new ActiveXObject('Msxml2.DOMDocument');
xmlDoc.load('list.xml');
var err=xmlDoc.parseError;
if(err.errorCode) { alert('XML 문서 해석 실패 : '+err.reason); return; }
...
}
</script>
...
<form name='form1'>
...
<input type='button' name='btnOpen' value='열기' onclick='javascript:btnOpen_Click()'/>
...
</form>
var xmlDoc;
function btnOpen_Click()
{
xmlDoc = new ActiveXObject('Msxml2.DOMDocument');
xmlDoc.load('list.xml');
var err=xmlDoc.parseError;
if(err.errorCode) { alert('XML 문서 해석 실패 : '+err.reason); return; }
...
}
</script>
...
<form name='form1'>
...
<input type='button' name='btnOpen' value='열기' onclick='javascript:btnOpen_Click()'/>
...
</form>
일부 소스는 생략했다.
new라는 것은 Java시간에 배웠는데, 객체를 생성하는 것이다. new Car, new Soonok 등 우리는 2학년 떄 지긋지긋하게 써 봤다.
ActiveXObject라는 객체를 이용, DOM 객체를 생성한다. 딴 거 필요없고 저렇게만 쓰면 된다.
다음은 load라는 method를 통해 파일을 불러온다. 나중에는 HTML에서 파일을 불러오면 이를 Javascript에 전달받아서 쓰게 되겠지만, 일단은 직접 파일명을 쳐 보자. 예제의 파일명은 'list.xml' 이다.
다음의 코드는 XML 로딩이 실패했을 경우 알려주는 것인데, parseError라는 속성을 별도의 변수에 저장한다. 이 속성은 에러가 났을 경우 에러 정보를 가지고 있다.
이 속성 하위에는 또 다른 속성이 있는데, errorCode라는 속성은 XML 로딩 시 에러 여부를 판별한다. 에러가 있을 경우 true, 없을 경우 false이다. if문을 통해 true일 경우 alert를 이용해 XML 해석이 실패했다는 것과 err.reason 이라는 속성을 통해 이유를 알려준다.
만약 XML 로딩이 성공적으로 완료되었으면 if문 다음부분을 통해 XML문서를 만지작할 수 있다.
우선, 처음 엘리먼트의 내용을 출력해 보자. 예제 XML 파일의 많은 내용 중 첫번째 엘리먼트의 내용은 '멋진남자 히라이켄' 이 된다. (kind='수필' 부분은 속성이다)
다음은 이 소스 전문(全文)이다.
<html><head>
<script language='JavaScript'>
var xmlDoc, eRoot;
function btnOpen_Click()
{
xmlDoc = new ActiveXObject('Msxml2.DOMDocument');
xmlDoc.load(form1.fileName.value);
var err=xmlDoc.parseError;
if(err.errorCode) { alert('XML 문서 해석 실패 : '+err.reason); return; }
eRoot = xmlDoc.documentElement;
var eBook = eRoot.firstChild;
displayBook(eBook); // 함수 호출
}
function displayBook(eBook)
{
var eTitle = eBook.firstChild;
var tTitle = eTitle.firstChild;
form1.txtTitle.value = tTitle.data;
}
</script>
</head><body>
<center>
<form name='form1'>
<h2>책 정보</h2>
<input type='file' size='30' name='fileName' />
<input type='button' name='btnOpen' value='열기' onclick='javascript:btnOpen_Click()'/>
<hr width='400' />
<table border='1' width='400' />
<tr>
<td bgcolor='#d0d0d0' width='100'>책제목</td>
<td><input type='text' name='txtTitle' size='40' /></td>
</tr>
</table>
</form>
</center>
</body></html>
<script language='JavaScript'>
var xmlDoc, eRoot;
function btnOpen_Click()
{
xmlDoc = new ActiveXObject('Msxml2.DOMDocument');
xmlDoc.load(form1.fileName.value);
var err=xmlDoc.parseError;
if(err.errorCode) { alert('XML 문서 해석 실패 : '+err.reason); return; }
eRoot = xmlDoc.documentElement;
var eBook = eRoot.firstChild;
displayBook(eBook); // 함수 호출
}
function displayBook(eBook)
{
var eTitle = eBook.firstChild;
var tTitle = eTitle.firstChild;
form1.txtTitle.value = tTitle.data;
}
</script>
</head><body>
<center>
<form name='form1'>
<h2>책 정보</h2>
<input type='file' size='30' name='fileName' />
<input type='button' name='btnOpen' value='열기' onclick='javascript:btnOpen_Click()'/>
<hr width='400' />
<table border='1' width='400' />
<tr>
<td bgcolor='#d0d0d0' width='100'>책제목</td>
<td><input type='text' name='txtTitle' size='40' /></td>
</tr>
</table>
</form>
</center>
</body></html>
실행 결과는 다음과 같다
첫번째로 친 줄은 원래 소스에서 직접 파일 이름을 썼던 부분인데, HTML에서 파일을 읽어들일 수 있도록 'input type=file' 을 이용하여 HTML에서 파일을 불러들이게 했고, 그 파일 경로가 저장되어 있는 'form1.fileName.value' 속성을 기존의 'xmlDoc.load' 메소드에 인수로 넣으면 그 파일이 불러져 만지작할 수 있게 된다.
이번에 선언한 변수는 eRoot, eBook, eTitle, tTitle 4개이다.
eRoot는 말 그대로 루트, 그러니까 루트 엘리먼트인 <booklist> 태그 부분이다.
eBook은 XML 파일에서 <book> 태그 부분이다.
eTitle은 <book> 태그 밑에 있는 <title> <author> 등의 엘리먼트들이다.
tTitle은 eTitle에 있는 값을 가지고 있어서, tTitle.data 라는 메소드를 이용하여 이 값을 얻을 수 있다.
각각의 변수에 있는 firstChild 속성은 맨 처음 엘리먼트(태그) 를 가리킨다. 그러므로 무조건 맨 위에 있는 <booklist> - <book> - <title> - '멋진남자 히라이켄' 으로 가게 된다.
우리의 목표는 XML 파일의 모든 것을 만지작하는 것이다. 그런데 겨우 무조건 맨 첫번째 엘리먼트만 만지작거리겠다고 하는 건 말이 안 된다. 이젠 다음 엘리먼트를 만지작거릴 차례다.
여기서는 nextSibling이라는 속성을 이용한다. 이 속성은 다음 엘리먼트에 대한 속성을 담고 있다.
function displayBook(eBook)
{
var eTitle = eBook.firstChild;
var tTitle = eTitle.firstChild;
form1.txtTitle.value = tTitle.data;
eTitle = eTitle.nextSibling;
tTitle = eTitle.firstChild;
form1.txtAuthor.value = tTitle.data;
}
-------------------------------------------------------
<tr>
<td bgcolor='#d0d0d0' width='100'>책제목</td>
<td><input type='text' name='txtTitle' size='40' /></td>
</tr>
<tr>
<td bgcolor='#d0d0d0' width='100'>책저자</td>
<td><input type='text' name='txtAuthor' size='40' /></td>
</tr>
{
var eTitle = eBook.firstChild;
var tTitle = eTitle.firstChild;
form1.txtTitle.value = tTitle.data;
eTitle = eTitle.nextSibling;
tTitle = eTitle.firstChild;
form1.txtAuthor.value = tTitle.data;
}
-------------------------------------------------------
<tr>
<td bgcolor='#d0d0d0' width='100'>책제목</td>
<td><input type='text' name='txtTitle' size='40' /></td>
</tr>
<tr>
<td bgcolor='#d0d0d0' width='100'>책저자</td>
<td><input type='text' name='txtAuthor' size='40' /></td>
</tr>
앞에서 빨갛게 칠해 진 부분을 이렇게 바꿔 nextSibling에 대해 알아보자.
먼제 책제목을 출력한 후, eTitle = eTitle.nextSibling을 통해 eTitle을 다음 엘리먼트, 즉 <title> 다음인 <author>에 위치시켰다. 데이터를 의미하는 tTitle은 데이터가 여러 개 있는 게 아니라 하나만 있으므로 firstChild로 한다. 그리고 출력.
책제목 | |
책저자 |
이렇게 <author>에 있던 '정민호' 라는 내용도 출력할 수 있게 되었다. 이를 응용하면, <publisher>와 <price> 엘리먼트의 내용도 모두 출력할 수 있다. nextSibling을 계속 해주면 된다.
이제 맨 처음의 <book> 엘리먼트(멋진남자 히라이켄 등등)를 모두 출력할 수 있게 되었다. 그런데, XML 예제 파일을 보면 '멋진남자 히라이켄' 외에도 '홀롤로' '대출의 정석' 등 모두 3개의 <book> 엘리먼트가 존재한다. 이들을 모두 출력할 순 없을까.
답은 간단하다. <book> 하위의 <title> -> <author> -> <publisher>.. 로 가는 데 eTitle에 NextSibling을 걸어 주었으니, 이제는 이 상위인 eBook에 nextSibling을 걸어주면 된다. 우리는 이를 지난번의 데이터 바인딩처럼 버튼으로 만들어, <book> 엘리먼트를 넘나들도록 만들어 보려고 한다.
일단 만들고자 하는 버튼은 '이전' 과 '다음' 이다. '다음' 은 nextSibling을 이용하면 되고, '이전' 은 next가 아닌 '앞' 이라는 뜻의 previousSibling이라는 속성을 이용하면 된다. 버튼을 이용하려면 Javascript 함수를 만들어야 하므로, 스크립트 내에 '이전' 과 '다음' 의 역할을 하는 새로운 함수를 만들자.
<script language='JavaScript'>
var xmlDoc, eRoot, eBook;
function btnOpen_Click()
{
xmlDoc = new ActiveXObject('Msxml2.DOMDocument');
xmlDoc.load(form1.fileName.value);
var err=xmlDoc.parseError;
if(err.errorCode) { alert('XML 문서 해석 실패 : '+err.reason); return; }
var xmlDoc, eRoot, eBook;
function btnOpen_Click()
{
xmlDoc = new ActiveXObject('Msxml2.DOMDocument');
xmlDoc.load(form1.fileName.value);
var err=xmlDoc.parseError;
if(err.errorCode) { alert('XML 문서 해석 실패 : '+err.reason); return; }
eRoot = xmlDoc.documentElement;
eBook = eRoot.firstChild;
displayBook(); // 함수 호출
}
eBook = eRoot.firstChild;
displayBook(); // 함수 호출
}
function displayBook()
{
--------- <중략> -----------
}
function btnPrev_Click()
{
eBook = eBook.previousSibling;
displayBook();
}
{
--------- <중략> -----------
}
function btnPrev_Click()
{
eBook = eBook.previousSibling;
displayBook();
}
function btnNext_Click()
{
eBook = eBook.nextSibling;
displayBook();
}
</script>
{
eBook = eBook.nextSibling;
displayBook();
}
</script>
일단 eBook을 전역변수로 바꿔버림에 따라, displayBook의 인수를 없앴다. 인수가 없어도 eBook이 전역변수이므로 쓸 수 있기 때문이다.
'이전' '다음' 버튼을 누르는 두 개의 함수는 previousSibling과 nextSibling을 적용한 이후 displayBook()으로 변경된 것을 재출력하도록 했다.
그렇다면 이젠 버튼을 만들어 보자. 밑의 소스는 맨 마지막 </td>와 </table> 사이에 넣으면 된다.
<tr>
<td colspan='2' align='center'>
<input type='button' name='btnPrev' value='이전' onclick='javascript:btnPrev_Click()'/>
<input type='button' name='btnNext' value='다음' onclick='javascript:btnNext_Click()'/>
</td>
</tr>
<td colspan='2' align='center'>
<input type='button' name='btnPrev' value='이전' onclick='javascript:btnPrev_Click()'/>
<input type='button' name='btnNext' value='다음' onclick='javascript:btnNext_Click()'/>
</td>
</tr>
이제 각 버튼을 눌러보면 '멋진남자 히라이켄'에서 '홀롤로' '대출의 정석' 으로 넘겨간 것을 확인할 수 있다.
그런데 만약, 맨 마지막 엘리먼트에서 nextSibling을 해 버리거나, 맨 처음 엘리먼트에서 previousSibling을 하게 되면 에러가 난다. 당연하지만 다음에 나올 데이터가 없기 때문에 에러가 나는 것이다.
이러한 것을 막으려면 현재 있는 eBook이 첫 또는 엘리먼트와 같은지의 여부를 조사해서 같으면 Sibling을 안 해버리면 된다.
function btnPrev_Click()
{
if(eBook != eRoot.firstChild)
{
eBook = eBook.previousSibling;
displayBook();
}
}
{
if(eBook != eRoot.firstChild)
{
eBook = eBook.previousSibling;
displayBook();
}
}
function btnNext_Click()
{
if(eBook != eRoot.lastChild)
{
eBook = eBook.nextSibling;
displayBook();
}
}
{
if(eBook != eRoot.lastChild)
{
eBook = eBook.nextSibling;
displayBook();
}
}
여기서 lastChild라는 속성이 새로 나오는데, firstChild이 맨 처음을 가리키는 속성이라면, lastChild은 말 그대로 맨 마지막 자식 엘리먼트를 가리키는 속성이 된다. 이것이 같으면, 결국 처음이나 마지막이라는 얘기다.
여기서 응용하여, '처음' '마지막' 버튼도 만들어 보자. eRoot.firstChild와 eRoot.lastChild를 이용하면 된다.
function btnFirst_Click()
{
eBook = eRoot.firstChild;
displayBook();
}
{
eBook = eRoot.firstChild;
displayBook();
}
function btnLast_Click()
{
eBook = eRoot.lastChild;
displayBook();
}
...
<input type='button' name='btnFirst' value='처음' onclick='javascript:btnFirst_Click()'/>
<input type='button' name='btnLast' value='마지막' onclick='javascript:btnLast_Click()'/>
{
eBook = eRoot.lastChild;
displayBook();
}
...
<input type='button' name='btnFirst' value='처음' onclick='javascript:btnFirst_Click()'/>
<input type='button' name='btnLast' value='마지막' onclick='javascript:btnLast_Click()'/>
이제 처음과 마지막 버튼을 눌러보면 정상 작동되는 것을 확인할 수 있다.
여기서 우리는 잊은 게 있었다. <book kind = ''>
속성 부분인데, 아무래도 엘리먼트 내에 있는 '멋진남자 히라이켄' 같은 데이터가 아니다 보니 보통의 방법으로는 할 수 없고, 새로운 메소드를 써서 속성값을 가져오면 된다.
displayBook() 함수에 다음을 추가한다.
form1.txtKind.value = eBook.getAttribute('kind');
getAttribute라는 메소드는 엘리먼트의 속성을 가져오기 위한 메소드이다. 'kind' 라는 인수를 넣어 kind속성을 불러들여온다. 이 함수에서 form1.txtKind라고 하였으므로 역시 HTML 안의 form 태그 내에 다음을 추가한다.
<tr>
<td bgcolor='#d0d0d0' width='100'>책종류</td>
<td><input type='text' name='txtKind' size='40' /></td>
</tr>
<td bgcolor='#d0d0d0' width='100'>책종류</td>
<td><input type='text' name='txtKind' size='40' /></td>
</tr>
한번 실행해 보자. 이젠 모든 내용을 출력할 수 있으며, 엘리먼트간 자유로운 이동도 할 수 있다.