주요글: 도커 시작하기
반응형
데이터의 교환 수단이 아닌 데이터의 표현 수단으로서 XML을 응용해본다.

풀다운 메뉴와 XML

XML은 이제 개발자들이 알아야할 하나의 기술이 되어가고 있는 것 같다. XML은 표준화된 데이터를 표현할 수 있는 특징을 갖고 있으며, XSL/T를 사용하여 손쉽게 다른 포맷으로 변경할 수 있다는 장점을 갖고 있기 때문에, XML-EDI와 같은 B2B 분야에서 그 진가를 조금씩 발휘하고 있다.

하지만, B2B와 같이 데이터를 교환하는 수단으로서만 XML이 가치 있는 것은 아니다. XML의 가장 기본적인 특징은 XML이 데이터를 표현하는 언어라는 점이다. 이 글에서는 XML을 데이터로 사용하여 웹 사이트에서 최근에 많이 사용되고 있는 풀다운 메뉴를 구현하는 방법에 대해서 살펴볼 것이다.

풀다운 메뉴는 이미 모든 컴퓨터 사용자에게 익숙한 GUI(Graphical User Interface)이다. 우리가 사용하고 있는 대부분의 응용프로그램은 사용자와의 인터페이스를 위해 대부분 풀다운 메뉴를 사용하고 있으며, 마이크로소프트의 웹 사이트를 비롯한 많은 웹 사이트가 풀 다운 메뉴를 사용하고 있다.

풀 다운 메뉴의 구조적 특징은 계층 구조를 갖는다는 점이다. 예를 들어, 다음 그림은 많이 사용되고 있는 웹브라우저인 인터넷 익스플로러의 메뉴 화면을 캡쳐한 화면이다.


위 그림을 보면 '편집'이라는 메뉴 밑에 '복사', '붙여넣기' 등의 메뉴 아이템이 존재하는 것을 알 수 있다. '파일', '보기'와 같은 다른 메뉴 역시 여러 메뉴 아이템을 포함하고 있다. 여기서 전체 메뉴 시스템은 계층적인 구조를 갖고 있다는 점을 알 수 있다. 계층적인 구조는 XML의 기본적인 특징으로서 XML은 태그를 중첩시킴으로써 계층적인 데이터를 표현할 수 있다. 예를 들어, 위 그림에 나타나 있는 메뉴의 구조를 XML을 사용하여 표현하면 다음과 같을 것이다.

<?xml version="1.0" encoding="euc-kr"?>
<root>
   <Menu>
      <MenuName>파일(F)</MenuName>
      <MenuItem>
         <MenuItemName>열기</MenuItemName>
      </MenuItem>
      <MenuItem>
         <MenuItemName>다른 이름으로 저장</MenuItemName>
      </MenuItem>
   </Menu>
   <Menu>
      <MenuName>편집</MenuName>
      <MenuItem>
         <MenuItemName>잘라내기</MenuItemName>
      </MenuItem>
      <MenuItem>
         <MenuItemName>복사</MenuItemName>
      </MenuItem>
      <MenuItem>
         <MenuItemName>붙여넣기</MenuItemName>
      </MenuItem>
   </Menu>
</root>

위 XML 문서를 살펴보면 <Menu> 요소는 메뉴 바에 있는 '파일', '편집'과 같은 메뉴를 나타내고, <Menu> 요소에 중첩되어 있는 <MenuName> 요소는 <Menu> 요소가 나타내는 메뉴의 이름을 나타낸다. 또한, <MenuItem> 요소는 그 요소가 중첩되어 있는 <Menu> 요소가 나태는 메뉴의 메뉴 아이템을 나타낸다. 여기서, XML이 계층적 구조를 쉽게 나타낼 수 있기 때문에 XML을 이용하여 풀다운 메뉴를 매우 간단하게 표현할 수 있다는 것을 알았을 것이다.

웹에서 사용될 풀다운 메뉴를 위한 XML 문서의 구조

웹에서 풀다운 메뉴를 구현할 경우, 각각의 메뉴와 메뉴 아이템은 관련된 링크를 갖게 된다. 이 링크를 나타내기 위해서는 단순히 앞에서 살펴본 XML 구조에 <HyperLink>와 같은 태그를 추가하기만 하면 된다. 예를 들면 다음과 같다.

<?xml version="1.0" encoding="euc-kr"?>
<root>
   <Menu>
      <MenuName>HOME</MenuName>
      <HyperLink>/main/index.jsp</HyperLink>
   </Menu>
   <Menu>
      <MenuName>Help</MenuName>
      <HyperLink>/help/index.jsp</HyperLink>
      <MenuItem>
         <MenuItemName>FAQ</MenuItemName>
         <HyperLink>/help/faq.html</HyperLink>
      </MenuItem>
      <MenuItem>
         <MenuItemName>About JavaCan</MenuItemName>
         <HyperLink>/help/aboutJavaCan.html</HyperLink>
      </MenuItem>
   </Menu>
   
</root>

위 XML을 살펴보면 <Menu> 요소와 <MenuItem> 요소는 각각 <HyperLink> 요소를 중첩하고 있으며, 각각의 <HyperLink>는 관련된 URL을 값으로 갖고 있는 것을 알 수 있다. DTD를 사용하여 위 구조를 명시적으로 정의할 수도 있지만, 이 글에서는 생략하기로 한다.

XMLMenu 클래스

이제 XMLMenu 클래스를 작성해보자. 이 클래스의 역할은 풀다운 메뉴를 나타내는 XML 문서를 읽어와 그것을 알맞게 HTML로 만들어주는 것이다. XMLMenu는 XML 문서를 DOM API를 통해서 읽어온다. DOM API는 XML 문서를 메모리상에 트리 구조로 읽어온다. 트리 구조는 그 자체가 계층적 구조이기 때문에 메뉴를 나타내는 XML 문서를 처리하기에 알맞다.

XMLMenu 클래스에서 핵심적인 메소드는 createMenu(String systemURI) 메소드와 generateHTML() 메소드이다. createMenu() 메소드는 파라미터로 메뉴 데이터를 저장하고 있는 XML 문서를 나타내는 URI를 받으며, 그 XML 문서를 로딩하여 분석한다. XMLMenu.createMenu() 메소드는 다음과 같다.

public void createMenu(String systemURI) throws SAXException, IOException {
   Document document = null;
   DOMParser parser = new DOMParser();   
   parser.parse(systemURI);
   document = parser.getDocument();
   
   // <Menu> 노드 목록을 구한다.
   NodeList nodeList = document.getDocumentElement().getChildNodes();
   Node node; // 현재 처리하고 있는 <Menu> 노드
   NodeList childNodes; // <Menu>의 자식 노드
   Node cNode; // 현재 처리하고 있는 <Menu>의 자식 노드
   Node cNode2; // 현재 처리하고 있는 <MenuName>의 자식 노드
   
   for (int i = 0 ; i < nodeList.getLength() ; i++) {
      node = nodeList.item(i);
      if ( node.getNodeType() == Node.ELEMENT_NODE &&
           node.getNodeName().compareTo("Menu") == 0) {
         // <Menu> element일 경우
         childNodes = node.getChildNodes();
         cNode = childNodes.item(1); // <MenuName> 노드
         String menuName = getNodeValue(cNode);
         cNode = childNodes.item(3); // <HyperLink> 노드
         String hyperLink = getNodeValue(cNode);
         
         menuList.add(menuName);
         menuLinkList.add(hyperLink);
         
         if (childNodes.getLength() > 5) {
            // <MenuItem> 노드가 있는 경우
            StringBuffer sb = new StringBuffer( (childNodes.getLength() - 5) * 250);
            
            for (int j = 5 ; j < childNodes.getLength() ; j++) {
               cNode = childNodes.item(j);
               if ( cNode.getNodeType() == Node.ELEMENT_NODE &&
                    cNode.getNodeName().compareTo("MenuItem") == 0) {
                  cNode2 = cNode.getChildNodes().item(1); // <MenuItemName>
                  String menuItemName = getNodeValue(cNode2);
                  cNode2 = cNode.getChildNodes().item(3); // <HyperLink>
                  String menuItemLink = getNodeValue(cNode2);
                  
                  String tempString =
"<span id=\""+menuName+"\" class=\""+menuItemSpanClass+"\">"+
"<a href=\""+menuItemLink+"\" class=\""+menuItemLinkClass+"\">"+menuItemName+
"</a></span><br>\n";
                  sb.append(tempString);
               }
            }
            menuItemList.add(sb.toString());
         } else {
            menuItemList.add("");
         }
      }
   }
   generateHTML();
}

createMenu() 메소드는 다음과 같은 순서로 작업을 진행한다.

  1. DOMParser를 사용하여 XML 문서를 읽어온다.
  2. <Menu> 노드의 목록을 구한 후, 이 노드의 자식 노드인 <MenuName>과 <HyperLink>의 값을 각각 menuList와 menuLinkList에 저장한다.
  3. <Menu> 노드가 <MenuItem> 노드를 갖고 있다면, <MenuItem> 노드를 읽어와 알맞은 처리를한다.
위 코드를 볼 때 주의해야 할점이 있다면, 공백문자(' ')나 '\n'과 같은 글자 역시 자식 노드가 된다는 점이다. 예를 들어, 다음과 같은 형태의 XML 문서가 있다고 해 보자.

   <Menu>
      <MenuName>HOME</MenuName>
      <HyperLink>/main/index.jsp</HyperLink>
   </Menu>

이 경우 <Menu> 노드는 <MenuName>노드와 <HyperLink> 노드를 자식 노드로 갖지만, <Menu>와 <MenuName>사이에 있는 '\n\t\t' 역시 자식 노드로 갖는다. 따라서 <Menu> 노드의 자식 노드중에서 <MenuName>노드를 구하기 위해서는 두번째 자식 노드를 참조해야만 한다. createMenu() 메소드에서 <Menu> 노드의 자식 노드인 <MenuName>과 <HyperLink> 노드를 구하는 코드를 살펴보면 이런 점을 알 수 있다.

createMenu() 메소드는 <MenuItem> 노드가 있을 경우, 그 자식 노드인 <MenuItemName>과 <HyperLink> 태그의 값을 이용하여 다음과 같은 형태의 HTML 태그를 생성한다.

<span id="[MenuName 노드의 값]" class="menuitemspan">
<a href="[HyperLink 노드의 값]" class="menuitemlink">[MenuItemName 노드의 값]</a></span><br>

위와 같은 형태의 태그는 menuItemList에 저장된다. createMenu() 메소드는 XML 문서에 있는 모든 노드를 알맞게 처리하면, generateHTML()메소드를 호출한다. generateHTML() 메소드는 createMenu() 메소드에서 분석한 결과를 이용하여 메뉴바와 각 메뉴와 관련된 메뉴 아이템 목록을 HTML 형식으로 출력한다. 메뉴바와 메뉴 아이템 목록은 HTML에서 레이어를 표현할 수 있도록 해 주는 <div> 태그를 사용하여 표시된다. 풀다운 메뉴를 구현하기 위해서는 마우스가 특정한 영역에 들어오거나 그 영역을 벗어나는 경우 알맞게 메뉴를 보여주어야 하기 때문에, 자바 스크립트를 사용해야 한다. 이 때 사용되는 자바 스크립트에 대한 내용은 관련링크에서 제공되는 소스 코드를 참조하기 바란다.

다음은 generateHTML() 메소드이다.

private void generateHTML() {
   menuList.trimToSize();
   menuLinkList.trimToSize();
   menuItemList.trimToSize();
   Object[] menuArray = menuList.toArray();
   Object[] menuLinkArray = menuLinkList.toArray();
   Object[] menuItemArray = menuItemList.toArray();
   
   StringBuffer output = new StringBuffer(1024*8); // buffer 8K
   output.append("<div id=\"menuBar\" class=\"").append(menuBarClass).append("\">");
   for (int i = 0 ; i < menuArray.length ; i++) {
      output.append(" <span id=\"m").append(i).append("\">")
            .append("<a href=\"").append(menuLinkArray[i].toString())
            .append("\" class=\"").append(menuLinkClass).append("\" ");
      
      if ( !menuItemArray[i].toString().equals("") ) {
         output.append("onMouseOver=\"viewMenu('m").append(i)
               .append("', 'menu").append(i).append("')\" ")
               .append("onMouseOut=\"checkBound('m").append(i)
               .append("', 'menu").append(i).append("')\"");
      }
      output.append(">");
      output.append(menuArray[i].toString()).append("</a> </span>");
      
      if ( i < menuArray.length - 1 )
         output.append(" | ");
   }
   output.append("</div>");
   
   menuBar = output.toString();   output.delete(0, output.length());
   
   for (int i = 0 ; i < menuItemArray.length ; i++) {
      String temp = menuItemArray[i].toString();
      if (!temp.equals("")) {
         output.append("<div id=\"menu").append(i).append("\" ")
               .append("class=\"").append(menuItemDivClass).append("\" ")
               .append("onMouseOut=\"checkBound('', 'menu")
               .append(i).append("')\">\n");
         output.append(temp);
         output.append("</div>");
      }
   }
   menuItemLayer = output.toString();   output.delete(0, output.length());
}

XMLMenu 클래스를 컴파일하기 위해서는 알맞은 XML 파서가 필요하다. 필자는 여기서 아파치의 Xerces 파서를 사용하였다. 아파치 Xerces 파서는 http://xml.apache.org에서 구할 수 있다. 사용가능한 파서에 대한 내용은 자바캔의 또 다른 기사인 'XML의 소개와 XML 관련 기술'를 참고하기 바란다.

XMLMenu의 사용

XMLMenu.generateHTML() 메소드를 살펴보면 HTML로 변환한 결과를 각각 menuBar와 menuItemLayer에 저장하는 것을 알 수 있다. menuBar는 메뉴바와 관련된 HTML 코드를 갖고 있으며, menuItemLayer는 각각의 메뉴와 관련된 HTML 레이어를 값으로 갖게 된다. 이 두 값은 각각 XMLMenu.getMenuBar() 메소드와 XMLMenu.getMenuItemLayer() 메소드를 통해서 구할 수 있다.

JSP 페이지나 서블릿에서 XMLMenu 클래스를 사용하는 예제는 다음과 같다.

<%@ page language="java" session="false" %>
<%@ page import="javacan.xml.xmlmenu.XMLMenu" %>
<%
   XMLMenu xmlMenu = new XMLMenu();
   String filepath = "file:///" + getServletContext().getRealPath("xmlmenu.xml");   try {
      xmlMenu.setCSSClassName("menubar", "menulink", 
        "menuitemdiv", "menuitemspan", "menuitemlink");
      xmlMenu.createMenu(filepath);   } catch(Exception ex) {
      ex.printStackTrace();
   }
   String menuBar = xmlMenu.getMenuBar();   String menuItemLayer = xmlMenu.getMenuItemLayer();%>
<html>
<head>
<title>XML Hierarchical Menus</title>
<link rel=STYLESHEET type="text/css" href="xmlmenu.css">
<Script Language="JavaScript" src="xmlmenu.js"></script>
</head>
<body id="body" bgcolor="#FFFFFF" 
         TOPMARGIN="0" LEFTMARGIN="0" MARGINWIDTH="0" MARGINHEIGHT="0" >
<center>
<table width="500">
<tr>
   <td><%= menuBar %></td>
</tr>
<tr>
   <td>위 메뉴 시스템은 xmlmenu.xml 문서를 XMLMenu 클래스를
          사용하여 만들어진 것입니다.</td>
</table>
</center><%= menuItemLayer %></body>
</html>

위 JSP 페이지를 웹 브라우저에서 출력한 결과는 다음과 같다.


위 그림에서 메뉴바는 XMLMenu.getMenuBar() 메소드의 리턴 값을 출력해서 생성되며, 각각의 메뉴에 마우스가 올라갈 때 나타내는 메뉴 아이템들은 XMLMenu.getMenuItemLayer() 메소드의 리턴값을 출력해서 생성된다.

위 코드에서 사용된 자바스크립트와 스타일쉬트 파일은 "xmlmenu.js"와 "xmlmenu.css"는 관련 링크에 있는 소크 코드를 통해서 구할 수 있다.

결론

이 글에서는 XML을 사용하여 웹 페이지의 풀다운 메뉴를 표현하는 것에 대해서 알아보았다. 이처럼 XML을 사용하여 메뉴를 생성할 경우의 장점은 메뉴를 변경하기 위해서 JSP나 서블릿의 코드를 변경할 필요가 없다는 점이다. 출력되는 모양의 경우 CSS 파일만 변경해주면 되며, 이벤트에 대한 반응을 다르게 하고 싶을 경우 자바스크립트 파일만 변경해주면 된다. 즉, 풀다운 메뉴 자체가 거의 완벽하게 모듈화되는 것이다.

XML의 응용 분야는 무수히 많다고 할 수 있다. 특히, 데이터를 표현하는 분야에서는 XML을 손쉽게 활용할 수 있다. 이 글에서는 그러한 활용 중의 하나만을 선보였을 뿐이다. 독자 여러분 스스로도 XML을 다양한 곳에서 활용하는 방법을 찾아보기 바란다.

관련링크:

+ Recent posts