 |  |
|
난이도 : 중급 Doug Tidwell, Technology Evangelist, IBM
2007 년 10 월 23 일 XPath 2.0과 XSLT 2.0에는 item 데이터 유형, to 연산자, 시퀀스(sequences)의 개념이 도입되었습니다. 이러한 기능들을 사용하여 XML 문서에서 고급 HTML 뷰를 생성하는 샘플 애플리케이션을 구현하고, XSLT 2.0의 새로운 기능을 사용하여 관리하기가 쉽도록 더욱 짧아진 스타일시트를 만들어봅시다. XSLT 2.0 데이터 유형화에 대해 설명한 다음, 새로운 <xsl:function> 엘리먼트를 사용하는 방법을 설명합니다.
XPath 2.0과 XSLT 2.0의 주요 개념들 중 하나는 모든 것이 시퀀스라는 점이다. XPath 1.0과 XSLT 1.0에서는 주로 노드의 트리로 작업했다. 파싱된 XML 문서는 문서 노드와 그 자손들을 포함하고 있는 트리였다. 이 노드들의 트리를 사용하여, 루트 엘리먼트의 노드를 비롯하여, 루트 엘리먼트의 자손들, 애트리뷰트, 형제를 찾을 수 있었다. (XML 파일의 루트 엘리먼트 외 주석이나 프로세싱 명령어는 루트 엘리먼트의 형제로 간주된다.)
XPath 2.0과 XSLT 2.0에서 XML 문서를 작업할 때, XPath 1.0과 XSLT 1.0의 트리 구조와 같은 방식으로 시퀀스를 사용한다. 이 시퀀스에는 단일 아이템(문서 노드)이 포함되어 있고, 이것을 같은 방식으로 사용한다. 하지만, 원자 값(atomic values)의 시퀀스를 생성할 수 있다. 16-팀 단일 예선 토너먼트용 데이터를 관리하는 샘플 애플리케이션에서 발췌한 Listing 1은 원자 값의 시퀀스 예제를 보여주고 있다.
Listing 1. 원자 값의 시퀀스
<xsl:variable name="seeds" as="xs:integer*">
<xsl:sequence
select="(1, 16, 8, 9, 5, 12, 4, 13, 6, 11, 3, 14, 7, 10, 2, 15)"/>
</xsl:variable> |
이 코드는 변수 $seeds를 정의한다. 새로운 <xsl:sequence> 엘리먼트는 여러분이 기대한 대로 아이템의 시퀀스를 정의한다. 이 경우, 아이템들은 XML Schema xs:integer이다. 새로운 as 애트리뷰트는 변수의 데이터 유형을 정의하고, 별표 (xs:integer*)는 이 시퀀스에 0 또는 그 이상의 정수가 포함되어 있다는 것을 의미한다. XPath 1.0과 XSLT 1.0에서, 16개의 다른 텍스트 노드를 만들고, 이 노드들을 변수로 그룹핑 했다. XPath 2.0과 XSLT 2.0에서는, 시퀀스가 1차원적 수의 어레이로서 작동하고, 바로 이것이 샘플 애플리케이션에서 필요한 것이다.
시퀀스는 두 개의 규칙들을 따른다. 우선, 다른 시퀀스들을 포함할 수 없다. 세 개의 아이템들 시퀀스에서 새로운 시퀀스를 만들고, 세 개 아이템의 또 다른 시퀀스를 만들면, 결과는 여섯 개 아이템의 새로운 시퀀스가 된다. 두 번째, 시퀀스들은 노드와 아이템들을 혼합할 수 있다. Listing 1의 원자 값들을 포함하고 있는 시퀀스를 만들고, 모든 <contestant> 엘리먼트들을 만들 수 있다. (Listing 2)
Listing 2. 노드와 원자 값들의 시퀀스
<xsl:variable name="seeds" as="item()*">
<xsl:for-each select="/bracket/contestants/contestant">
<xsl:copy-of select="."/>
</xsl:for-each>
<xsl:sequence
select="(1, 16, 8, 9, 5, 12, 4, 13, 6, 11, 3, 14, 7, 10, 2, 15)"/>
</xsl:variable> |
$seeds 변수에는 모든 참가자 노드와 이전에 사용했던 16개의 원자 값들이 포함되어 있다. 이 변수의 데이터 유형은 item()*이다. 이 아이템은 노드 또는 원자 값이기 때문에, 변수에는 무엇이나 포함시킬 수 있다.
이제, 시퀀스와 아이템의 기초를 이해했으므로, XPath 2.0과 XSLT 2.0에 새롭게 도입된 to 연산자에 대해 살펴보자. 일정 범위의 정수를 선택할 수 있도록 해준다. 예를 들어, Listing 3과 같은 시퀀스를 만들 수 있다.
Listing 3. to 연산자를 사용하여 생성된 정수 시퀀스
<xsl:variable name="range" as="item()*">
<xsl:sequence select="1 to 16"/>
</xsl:variable> |
이 코드는 1부터 16까지의 정수를 포함하고 있는 $range라는 이름의 변수를 만든다. 샘플 애플리케이션에서, to 연산자를 루핑(looping) 장치로 사용할 수 있다. (Listing 4)
Listing 4. 루핑에 to 연산자 사용하기
<xsl:for-each select="1 to 32">
<!-- Do something useful here -->
</xsl:for-each> |
스타일시트를 구현하기 전에, 샘플 애플리케이션을 자세히 살펴보도록 하자.
샘플 애플리케이션 이해하기
이 글의 샘플 애플리케이션에서는 16-team 예선 토너먼트용 데이터를 관리하게 될 것이다. 여러분이 예상한 대로, 이 토너먼트 데이터는 XML로 표현된다. XML 데이터를 토너먼트의 결과를 나타내는 HTML 테이블로 변형하는 XSLT 2.0 스타일시트를 만들 것이다. Listing 5는 XML 문서 포맷이다.
Listing 5. 토너먼트 데이터가 있는 XML 문서
<?xml version="1.0" encoding="UTF-8"?>
<!-- tourney.xml -->
<bracket>
<title>RSDC Smackdown</title>
<contestants>
<contestant seed="1" image="images/homerSimpson.png">Donuts</contestant>
<contestant seed="2" image="images/caffeine.png">Caffeine</contestant>
<contestant seed="3" image="images/fearlessFreep.png">Fearless Freep</contestant>
<contestant seed="4" image="images/wmd.jpg">Weapons of Mass Destruction</contestant>
<contestant seed="5" image="images/haroldPie.jpg">Pie</contestant>
<contestant seed="6" image="images/adamAnt.png">Adam Ant</contestant>
<contestant seed="7" image="images/georgeWBush.jpg">Misunderestimated</contestant>
<contestant seed="8" image="images/sillyPutty.jpg">Silly Putty</contestant>
<contestant seed="9" image="images/krazyGlue.jpg">Krazy Glue</contestant>
<contestant seed="10" image="images/snoopDogg.png">Biz-Implification</contestant>
<contestant seed="11" image="images/atomAnt.png">Atom Ant</contestant>
<contestant seed="12" image="images/ajaxcan.png">AJAX</contestant>
<contestant seed="13" image="images/darthVader.jpg">Darth Vader</contestant>
<contestant seed="14" image="images/nastyCanasta.png">Nasty Canasta</contestant>
<contestant seed="15" image="images/jcp.png">Java Community Process</contestant>
<contestant seed="16" image="images/andre.png">Andre the Giant</contestant>
</contestants>
<results>
<result round="1" firstSeed="1" secondSeed="16" winnerSeed="1"/>
<result round="1" firstSeed="8" secondSeed="9" winnerSeed="9"/>
<result round="1" firstSeed="5" secondSeed="12" winnerSeed="5"/>
<result round="1" firstSeed="4" secondSeed="13" winnerSeed="4"/>
<result round="1" firstSeed="6" secondSeed="11" winnerSeed="11"/>
<result round="1" firstSeed="3" secondSeed="14" winnerSeed="3"/>
<result round="1" firstSeed="7" secondSeed="10" winnerSeed="10"/>
<result round="1" firstSeed="2" secondSeed="15" winnerSeed="2"/>
<result round="2" firstSeed="1" secondSeed="9" winnerSeed="1"/>
<result round="2" firstSeed="5" secondSeed="4" winnerSeed="5"/>
<result round="2" firstSeed="11" secondSeed="3" winnerSeed="3"/>
<result round="2" firstSeed="10" secondSeed="2" winnerSeed="2"/>
<result round="3" firstSeed="1" secondSeed="5" winnerSeed="1"/>
<result round="3" firstSeed="3" secondSeed="2" winnerSeed="2"/>
<result round="4" firstSeed="1" secondSeed="2" winnerSeed="2"/>
</results>
</bracket> |
<title> 엘리먼트에서, 토너먼트의 이름인 RSDC Smackdown을 볼 수 있다. 이 문서는 올해의 IBM Rational Software Developer Conference의 한 세션에서 나온 실제 결과를 나타내고 있다.
하나의 괄호에는 16개의 <contestant> 엘리먼트가 포함되어 있고, 각각 이름(텍스트), 시드(seed), 이미지를 갖고 있다. 16-팀 토너먼트는 15개의 매치업(matchup)들로 구성된다. 각 매치업은 <result> 엘리먼트로 표현된다. 네 조각의 데이터들은 각각의 매치업과 제휴된다: 매치업이 발생했던 토너먼트의 라운드(round 애트리뷰트), 두 경쟁자들의 시드(firstSeed와 secondSeed에 저장됨), 우승자의 시드(winnerSeed 애트리뷰트). 여러분이 해야 할 일은 XML 문서를 가져다가 결과를 나타내는 HTML 테이블로 변형하는 것이다. (그림 1)
그림 1. HTML 테이블로 나타난 토너먼트 결과
이 테이블에는 32개의 행과 다섯 개의 컬럼이 있다. XSLT 1.0 방식을 사용하여, 한 번에 한 행씩 HTML 테이블을 구현할 수 있다. (Listing 6)
Listing 6. 한 번에 한 행씩 HTML 테이블 구현하기
<!-- Row 1 -->
<tr>
<td style="border: none; border-top: solid; border-right: solid;">
<xsl:text>[1] </xsl:text>
<xsl:value-of select="$contestants[@seed='1']/>
</td>
<td style="border: none;>
</td>
<td style="border: none;>
</td>
<td style="border: none;>
</td>
<td style="border: none;>
</td>
</tr>
<!-- Row 2 -->
. . . |
이것도 유효하지만, 이 스타일시트는 관리하기 어렵다. 각 행과 컬럼에 대한 코드는 스타일시트를 통해 반복된다. 여기에서 가장 큰 문제는 아웃풋 테이블에 32개의 행이 필요하다는 점이다. 32개 행 각각에는 XML 문서의 엘리먼트에서 온 데이터가 포함된다. (<contestant> 또는 <result>). 안타깝게도, 반복할 수 있는 32개 엘리먼트가 없다. <xsl:for-each select="contestants/contestant|results/result">를 사용하겠지만, 이러한 엘리먼트들은 테이블에 필요한 순서대로 나타나지 않는다. XSLT 1.0은 이 문제를 해결할 툴이 없다.
코드를 리팩토링 하여 스타일시트를 더욱 단순하게 만들 수 있다. 셀의 스타일과 콘텐트에 패턴이 있고, XPath 2.0과 XSLT 2.0의 새로운 기능을 사용하여 그러한 패턴들을 통해 반복할 수 있다. 마지막 스타일시트는 원래 버전보다 약 70% 작다. 스타일시트를 구현하기 전에, 코드 리팩토링 방법을 살펴보자.
코드 리팩토링 - 테이블 셀 스타일 계산하기
코드를 리팩토링 하려면, 스타일 정보를 CSS 스타일시트로 옮기는 것부터 시작한다. 그림 2에서 보듯, HTML 브래킷(bracket) 테이블에는 다섯 개의 다른 셀 스타일이 있다: None, MatchupStart, MatchupMiddle, MatchupEnd, Solid.
그림 2. HTML 테이블의 셀 보더 스타일
Listing 7은 CSS 코드 모습이다.
Listing 7. CSS 스타일시트
.None { width: 20%; }
.MatchupStart { width: 20%;
border: none; border-top: solid;
border-right: solid; }
.MatchupMiddle { width: 20%;
border: none; border-right: solid; }
.MatchupEnd { width: 20%;
border: none; border-bottom: solid;
border-right: solid; }
.Solid { width: 20%;
border: solid; } |
보더(border) 스타일로는 토너먼트의 결과를 더욱 쉽게 볼 수 있다. 두 개의 셀들을 결합한 수평 라인들은 이 두 개의 경쟁자들이 대결한다는 것을 나타내고 있다. 다음 컬럼의 굵은(solid) 보더로 된 셀은 매치업의 우승자를 나타낸다. 보더 스타일을 보면서, 일정한 패턴을 알 수 있다. 각 쌍의 경쟁자들의 경우, 첫 번째 경쟁자 앞의 셀은 보더가 없고(style None), 두 명의 경쟁자들에 대한 셀은 굵은 보더(style Solid)를 갖고 있고, 두 명의 경쟁자들간 셀은 오른쪽에 보더를 갖고 있으며(style MatchupMiddle), 마지막 경쟁자 뒤의 셀은 보더가 없다(style None). 이는 첫 번째 컬럼과는 약간 다르다. 첫 번째 컬럼에서 한 쌍의 경쟁자들은 서로 너무 가깝기 때문에, 첫 번째 경쟁자용 셀은 위와 오른쪽에 보더를 갖고 있고(style MatchupStart), 두 번째 경쟁자를 위한 셀은 아래와 오른쪽에 보더를 갖고 있다(style MatchupEnd).
경쟁자들의 쌍은 각 컬럼에서 더 멀리 떨어져있다. 한 셀(one-cell)간 간격은 컬럼 1에서는 경쟁자들간 간격, 컬럼 2에서는 세 개의 셀 간격, 컬럼 3에서는 7 개의 셀 간격, 컬럼 4에서는 15 개의 셀 간격이다. 각 간격의 크기는 2의 제곱에 하나를 뺀 것이고, 이것이 일종의 패턴이 된다.
테이블에 생성된 패턴은 각 컬럼에 반복되는 셀 그룹이 포함되도록 한다. 반복되는 그룹의 크기(필자는 적절한 단어를 찾다가 피리어드(period)라는 말을 쓰기로 했다.)는 다섯 개의 컬럼들 각각 4, 8, 16, 32, 64이다. 각각의 반복되는 그룹에는 두 명의 경쟁자들, 두 명의 경쟁자들 간 셀, 두 명의 경쟁자들 전후의 셀이 포함된다.
각 컬럼 안에서, 계산에 두 개의 값을 사용한다. $period는 반복되는 그룹의 크기를 나타내고, $oneQuarter는 피리어드 크기의 1/4이다. (변수에 $period div 4를 저장하면 코드는 더욱 깔끔해진다.) 표 1은 셀 보더 스타일 규칙을 나타낸다.
표 1. HTML 테이블로 표현된 셀 보더 스타일
| Formula | Column 1 (period 4) | Column 2 (period 8) | Column 3 (period 16) | Column 4 (period 32) | Column 5 (period 64) |
|---|
$row < $oneQuarter or $row > $period - $oneQuarter
| N/A |
Row 1, style None
|
Rows 1-3, style None
|
Rows 1-7, style None
|
Rows 1-15, style None
|
$row = $oneQuarter
|
Row 1, style MatchupStart
|
Row 2, style Solid
|
Row 4, style Solid
|
Row 8, style Solid
|
Row 16, style Solid
|
$row > $oneQuarter and $row < $period - $oneQuarter
|
Row 2, style MatchupMiddle
|
Rows 3-5, style MatchupMiddle
|
Rows 5-11, style MatchupMiddle
|
Rows 9-23, style MatchupMiddle
|
Rows 17-32, style None
|
$row = $period - $oneQuarter
|
Row 3, style MatchupEnd
|
Row 6, style Solid
|
Row 12, style Solid
|
Row 24, style Solid
| N/A |
$row < $oneQuarter or $row > $period - $oneQuarter
|
Row 4, style None
|
Rows 7-8, style None
|
Rows 13-16, style None
|
Rows 25-32, style None
| N/A |
테이블에 있는 셀의 스타일을 파악하는 방식을 배웠다. 컬럼 수와 행 수가 주어진다면, 더욱 매력적인 공식이 될 것이다.
코드 리팩토링 - 테이블 셀 콘텐트 계산하기
테이블에 셀을 만들 때, 그 셀에 올바른 콘텐트를 넣어야 한다. 이것 역시 좋은 패턴을 갖고 있다. None 또는 MatchupMiddle 스타일에는 어떤 콘텐트도 없다. 다른 세 개의 스타일을 가진 셀에 대한 올바른 콘텐트만 찾으면 된다.
표 1에서, 콘텐트가 있는 셀은 $row = $oneQuarter or $row = $period - $oneQuarter 프로퍼티를 갖고 있다는 것을 알 수 있다. 첫 번째 컬럼의 경우, 시드와 올바른 경쟁자의 이름을 작성한다. 매치업은 시드(seeding)에 기반하고 각각 짝을 이루고 있다. (표 2)
표 2. 시드에 기반한 매치업
| [1] versus [16] | | [8] versus [9] | | [5] versus [12] | | [4] versus [13] | | [6] versus [11] | | [3] versus [14] | | [7] versus [10] | | [2] versus [15] |
더 높은 시드가 이긴다면, 탑-시드(top-seeded) 팀은 언제나 나머지 가장 낮은 시드를 받은 팀과 겨루고, 두 번째 시드를 받은 팀은 두 번째로 낮은 시드를 받은 팀과 겨루도록 매치업이 정렬된다. 이러한 순서대로 시드를 보다 보면 (1, 16, 8, 9, 5, 12, 4, 13, 6, 11, 3, 14, 7, 10, 2, 15), 이것이 시퀀스 $seeds의 값과 매치한다는 것을 알 수 있다. 경쟁자들은 이러한 순서로 컬럼 1에 디스플레이 된다.
하나의 경쟁자는 하나 걸러 다음 행에 나타나는데, 행 1은 이 시퀀스에 첫 번째 시드를 갖고 있고, 행 3은 시퀀스에서 두 번째 시드를 갖고 있고, 행 5는 시퀀스에서 세 번째 시드를 갖고 있다. 이 패턴은 $row + 1 div 2 = $index를 갖고 있다. 다시 말해서, 행 넘버에 1을 더하고, 이것을 2로 나누어서 $seeds 시퀀스 내에서 시드의 인덱스를 얻는다. 테이블 셀의 콘텐트는 시드 넘버와 그 시드에 있는 경쟁자의 이름이다.
이것은 컬럼 1의 콘텐트를 관리한다. 컬럼 2부터 5까지는 좀더 복잡하다. <contestant> 엘리먼트를 보는 대신, 결과를 보아야 하는데, 이것은 15 개의 <result> 엘리먼트에 디스플레이 된다.
컬럼 2에는 1라운드의 우승자가 포함된다. round="1" 애트리뷰트를 가진 <result> 엘리먼트를 봐야 한다는 것을 의미한다. 단순하게 하려면, 첫 번째 매치업(1 versus 16 시드)의 우승자는 첫 번째 <result> 엘리먼트에 저장되고, 두 번째 매치업(8 versus 9 시드)의 우승자는 두 번째 <result> 엘리먼트에 저장된다. 컬럼 2의 우승자는 2, 6, 10, 14, 18, 22, 26, 30 행에 디스플레이 된다. 패턴을 찾으면서, 행 2는 첫 번째 <result> 엘리먼트를 사용하고, 행 6은 두 번째를, 행 10은 세 번째를 사용한다. 이 컬럼의 경우, 행 넘버를 가져다가 2를 더하고, 이를 4로 나눈다면, 올바른 <result round="1"> 엘리먼트의 위치를 파악할 수 있다.
컬럼 3과 4는 비슷하게 핸들된다. 컬럼 3의 우승자는 행 4, 12, 20, 28에 디스플레이 된다. (패턴은 4를 더하고 8로 나눈다.) 컬럼 4의 우승자는 행 8과 24에 디스플레이 된다. (8을 더하고 16으로 나눈다.) 컬럼 5는 단 하나의 경쟁자—전체 토너먼트의 우승자만 디스플레이 한다. 행 16에 있다면, 하나의 <result round="4"> 엘리먼트에서 우승자를 디스플레이 한다.
여러분이 찾고 있는 값의 위치를 찾는 방식은 ($row + $oneQuarter) div ($oneQuarter * 2)이다. 반복되는 패턴의 증가 사이즈를 사용하면 코드는 단순해 진다. 컬럼 1의 경우, 계산된 위치는 시드의 시퀀스에 대한 인덱스이다. 다른 컬럼의 경우, 계산된 위치는 적절한 <result> 엘리먼트의 위치이다.
테이블에 있는 각 셀의 콘텐트와 스타일을 결정하는 방법을 알아 보았다. 행과 컬럼 넘버가 있다면 모든 것을 규명할 수 있다.
XPath 2.0과 XSLT 2.0의 힘
XPath 2.0과 XSLT 2.0의 새로운 기술을 사용하여 스타일시트를 체계화 할 수 있다. 시작하려면, to 연산자를 사용한다. 절차적 프로그래밍 언어로 테이블을 구현했다면 Listing 8과 비슷하게 해야 한다.
Listing 8. 스타일시트에 대한 절차적 방식
for (int row=1; row<=32; row++)
for (int column=1; column<=5; column++)
// Build each cell in the table here |
XPath 2.0과 XSLT 2.0에서, 절차적 언어에서 사용하는 for 루프에 해당하는 <xsl:for-each>를 사용한다. Listing 9는 그 방법을 보여주고 있다.
Listing 9. to 연산자로 for 루프 수행하기
<xsl:for-each select="1 to 32">
<xsl:variable name="outerIndex" select="."/>
<tr>
<xsl:for-each select="1 to 5"> |
여기에서 두 가지 복잡한 문제들이 발견된다. 우선, XPath 1.0과 XSLT 1.0에서, <xsl:for-each>를 사용하여 각 반복의 정황을 수정했다. 예를 들어, <xsl:for-each
select="contestants/contestant>를 사용했다면, 콘텍스트 노드는 각 반복 동안 최신의 <contestant>이다. to 연산자를 사용하여 다양한 정수들을 반복하기 때문에, 콘텍스트 아이템(2.0에서는 콘텍스트 아이템이다.)이 정의되지 않는다. Listing 9에서 보듯, 밖에 있는 <xsl:for-each>의 현재 값을 저장한다. 내부 <xsl:for-each>에서는 사용할 수 없기 때문이다.
하지만, 무엇인가 잘못되었다. 콘텍스트 아이템이 정의되지 않는다면, 문서에서 노드를 선택할 방법이 없다. 행 1과 컬럼 1에 있다는 것을 알고 있다면, $seeds 시퀀스에서 첫 번째 아이템을 가져올 수 있다. $seeds는 글로벌 변수이기 때문이다. 이것은 여러분이 <contestant seed="1"> 엘리먼트를 찾아야 한다는 것을 말해주고 있다. 안타깝게도, 이 문서에서는 어떤 것도 얻을 수 없다. 심지어, /bracket/contestants/contestant[@seed='1'] 같은 XPath 식을 사용하더라도 소용이 없다. 이와 같은 이유로, 여러분이 관리하는 노드를 글로벌 변수로서 저장해야 한다. Listing 10은 필요할 때 어디서나 글로벌 변수에 액세스 하는 방법을 보여주고 있다.
Listing 10. 필요한 노드를 저장하는 글로벌 변수
<xsl:variable name="results" select="/bracket/results"/>
<xsl:variable name="contestants" select="/bracket/contestants"/> |
16번째 시드 경쟁자의 이름을 알고 싶다면, XPath 식은 $contestants/contestant[@seed="16"]이 된다. 이와 비슷하게 <result> 엘리먼트에 액세스 한다; 라운드 2에서 두 번째 매치업의 우승자를 얻어야 한다면, 식은 $results/result[@round="2"][2]/@winnerSeed가 된다. Listing 11은 HTML 테이블을 생성하기 위해 사용할 수 있는 두 개의 글로벌 변수들이다.
Listing 11. 기타 유용한 글로벌 변수들
<xsl:variable name="periods" as="xs:integer*">
<xsl:sequence select="(4, 8, 16, 32, 64)"/>
</xsl:variable>
<xsl:variable name="backgroundColors" as="xs:string*">
<xsl:sequence
select="('background: #CCCCCC;', 'background: #9999CC;',
'background: #99CCCC;', 'background: #CC99CC;',
'background: #CCCC99;')"/>
</xsl:variable> |
이러한 변수들은 각 컬럼에 대한 피리어드 값과 각 컬럼의 백그라운드 컬러를 저장한다. 각 변수를 사용하려면, 컬럼 넘버를 단일 값의 인덱스로서 사용한다.
XPath 2.0과 XSLT 2.0의 또 다른 향상은 <xsl:function> 엘리먼트에 있다. 스타일시트에 두 개의 함수, cellStyle과 getResults를 만들어 보자. 첫 번째 함수는 각 셀에 대한 보더 스타일을 리턴한다. 두 번째 함수는 주어진 매치업에 대한 결과를 리턴한다. 두 함수에 대한 매개변수들은 셀의 행과 컬럼 번호이다. Listing 12는 cellStyle 함수용 코드이다.
Listing 12. cellStyle 함수
<xsl:function name="bracket:cellStyle" as="xs:string">
<xsl:param name="row" as="xs:integer"/>
<xsl:param name="column" as="xs:integer"/>
<xsl:variable name="period" as="xs:integer"
select="subsequence($periods, $column, 1)"/>
<xsl:variable name="oneQuarter" as="xs:integer"
select="$period div 4"/>
<xsl:variable name="lastColumn" as="xs:boolean"
select="$oneQuarter = count($contestants/contestant)"/>
<xsl:variable name="position" select="$row mod $period"/>
<xsl:value-of
select="if ($position = $oneQuarter) then
(if ($column = 1) then 'MatchupStart'
else 'Solid')
else if ($position = $period - $oneQuarter) then
(if ($column = 1) then 'MatchupEnd'
else 'Solid')
else if ($lastColumn) then 'None'
else if ($position < $oneQuarter or
$position > $period - $oneQuarter) then 'None'
else 'MatchupMiddle'"/>
</xsl:function> |
셀 스타일을 결정하기 전에, 네 개의 변수를 사용한다. 글로벌 변수 $periods에서 $period의 값을 검색한다. 전에 언급했지만, $oneQuarter는 $period div 4이다. Boolean 값 $lastColumn은 $oneQuarter를 토너먼트의 경쟁자의 수와 비교한다. 이러한 값들이 동일하다면, 마지막 컬럼을 처리한다. 마지막으로, $position 변수는 패턴 내의 현재 행의 위치를 가리킨다. 다시 말해서, 테이블의 행 9는 컬럼 1과 2에 반복되는 그룹의 첫 번째 행이다. 패턴 내의 행의 위치를 사용하여 셀 스타일을 규명한다.
XPath 2.0과 XSLT 2.0의 주요 특징 중에는 if 연산자가 있는데, 이것은 (괄호 안에) 식을 갖고 있고, 그 뒤에 then과 else가 뒤따른다. 이 모든 것이 <xsl:value-of>의 select 애트리뷰트로 간다. 이 예제에서, 훨씬 더 장황한 <xsl:choose> 엘리먼트를 하나의 식으로 대체한다. 테이블 식의 관점에서 볼 때 코드는 매우 단순하다. 로직이 더 복잡하다면, 더 많은 타이핑이 필요하겠지만, <xsl:choose>를 사용하는 것이 쉬운 관리에 더욱 유용하다.
<xsl:function>도 주목할 만 하다. 우선, 이 함수에 대한 새로운 네임스페이스를 선언해야 한다. XPath 식에서 bracket:cellStyle()을 호출하면, 네임스페이스는 XSLT 2.0 프로세서에게 이 함수를 찾는 방법을 알려준다. 두 번째로, as="xs:string" 애트리뷰트를 사용하여 이 함수가 스트링을 리턴한다는 것을 가리킨다는 것에 주목하라. <xsl:value-of> 엘리먼트는 다섯 개의 스타일 이름 중 하나를 리턴한다. 이것이 함수의 아웃풋이다.
getResults 함수는 좀더 복잡하다. Listing 13을 보자.
Listing 13. getResults 함수
<xsl:function name="bracket:getResults" as="xs:string">
<xsl:param name="row" as="xs:integer"/>
<xsl:param name="column" as="xs:integer"/>
<xsl:variable name="period" as="xs:integer"
select="subsequence($periods, $column, 1)"/>
<xsl:variable name="oneQuarter" as="xs:integer"
select="$period div 4"/>
<xsl:variable name="position" select="$row mod $period"/>
<xsl:choose>
<xsl:when test="$position = $oneQuarter
or
$position = $period - $oneQuarter">
<xsl:variable name="round" select="$column - 1"/>
<xsl:variable name="index"
select="($row + $oneQuarter) div ($oneQuarter * 2)"/>
<xsl:variable name="currentSeed"
select="if ($column = 1) then
subsequence($seeds, $index, 1)
else
$results/result[@round=$round][$index]/@winnerSeed"/>
<xsl:choose>
<xsl:when test="string-length(string($currentSeed))">
<xsl:value-of
select="concat('[', $currentSeed, '] ',
$contestants/contestant[@seed=$currentSeed])"/>
</xsl:when>
<xsl:otherwise>
<xsl:text> </xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:text> </xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:function> |
전과 같은 매개변수이다. 여기에서는, <contestant> 엘리먼트에서 데이터를 포함하고 있는 컬럼 1에 대해 약간 특별한 프로세싱을 수행하는데, 다른 컬럼에는 <result> 엘리먼트에서 온 데이터를 포함하고 있다. cellStyle 함수와 마찬가지로, $period와 $position을 계산하고, $oneQuarter 변수를 사용하여 식을 단순화 한다.
호출에 한 명의 경쟁자가 있다면, 세 개 이상의 변수들을 계산한다. $round 변수는 컬럼 보다 적고(컬럼 2에는 라운드 1의 결과를 포함하고 있다.), 앞서 언급한 식에 따라 $index 변수를 계산한다.
두 단계를 따라 올바른 데이터를 찾는다. 먼저, $currentSeed 변수의 값을 설정한다. 이것이 1 라운드라면, 새로운 subsequence 함수를 사용하여 $seeds 변수에서 하나의 값을 선택한다. 다른 라운드의 경우, 적절한 <result> 엘리먼트에서 winnerSeed 애트리뷰트를 가져온다.
두 번째, 컬럼 1을 다르게 처리하는 것 외에도, 우승자를 갖고 있지 않은(winnerSeed="") <result>엘리먼트의 가능성도 염두해야 한다. 이 같은 일이 발생하면 nonbreaking space ( )를 리턴한다. XSLT 2.0이 데이터 유형을 핸들하는 방식 때문에(다음 기술자료에서 다룰 주제임), $currentSeed를 스트링으로 전환하고, 스트링의 길이를 테스트 한다. 스트링의 길이가 0이면, nonbreaking space를 리턴한다. 그렇지 않으면, 경쟁자의 시드와 이름을 리턴한다.
마지막으로, <xsl:choose>를 사용하고 있다는 것에 주목하라. 하나의 XPath if 문으로 무엇이든 할 수 있지만, 코드가 볼품 없다. <xsl:choose>가 보다 장황하지만, 코드는 더 깨끗하고 이해하기가 더 쉽다.
필요한 글로벌 변수와 함수를 설정하니, 스타일시트가 단순하고 고급스러워졌다. (Listing 14)
Listing 14. 스타일시트의 중심
<xsl:for-each select="1 to 32">
<xsl:variable name="outerIndex" select="."/>
<tr>
<xsl:for-each select="1 to 5">
<td style="{subsequence($backgroundColors, ., 1)}"
class="{bracket:cellStyle($outerIndex, .)}">
<xsl:value-of
select="bracket:getResults($outerIndex, .)"/>
</td>
</xsl:for-each>
</tr>
</xsl:for-each> |
32개의 행들에 다섯 개의 컬럼을 생성하는 두 개의 루프가 생겼다. 각 셀의 경우, 백그라운드 컬러는 현재 컬럼 넘버에 기반하고 있다. cellStyle 함수는 보더 스타일(class="x")을 정하고, getResults 함수는 셀의 값을 정한다.
스타일시트 사용하기
여러분이 예상하듯, 이 스타일시트를 사용하려면 XSLT 2.0 프로세서가 필요하다. 여기에서 전체 스타일시트를 보여주지는 않겠지만, 여러분은 <xsl:stylesheet version="2.0">을 사용하여 프로세서가 XSLT 2.0 모드에서 실행되도록 해야 한다. Michael Kay의 Saxon 프로세서(참고자료)를 사용할 것을 권장한다. Kay 박사는 XSLT 2.0 스팩의 에디터였고, Saxon은 스팩이 개발될 때의 테스트 케이스였다. Saxon을 사용한다면, 아래 명령어를 사용하여 results-html.xsl 스타일시트를 사용하는 tourney.xml XML 파일을 변형하고, 아웃풋을 results.html 파일에 쓴다:
java net.sf.saxon.Transform -o results.html tourney.xml results-html.xsl
스타일시트의 유연성을 설명하기 위해, XML 파일의 결과로 변형을 실행해 보았다. Listing 15는 <result> 엘리먼트 모습이다.
Listing 15. 불완전한 토너먼트 데이터가 있는 XML 문서
<?xml version="1.0" encoding="UTF-8"?>
<!-- incomplete-tourney.xml -->
<bracket>
. . .
<results>
<result round="1" firstSeed="1" secondSeed="16" winnerSeed="1"/>
<result round="1" firstSeed="8" secondSeed="9" winnerSeed="9"/>
<result round="1" firstSeed="5" secondSeed="12" winnerSeed="5"/>
<result round="1" firstSeed="4" secondSeed="13" winnerSeed="4"/>
<result round="1" firstSeed="6" secondSeed="11" winnerSeed="11"/>
<result round="1" firstSeed="3" secondSeed="14" winnerSeed="3"/>
<result round="1" firstSeed="7" secondSeed="10" winnerSeed="10"/>
<result round="1" firstSeed="2" secondSeed="15" winnerSeed="2"/>
<result round="2" firstSeed="" secondSeed="" winnerSeed=""/>
<result round="2" firstSeed="" secondSeed="" winnerSeed=""/>
<result round="2" firstSeed="" secondSeed="" winnerSeed=""/>
<result round="2" firstSeed="" secondSeed="" winnerSeed=""/>
<result round="3" firstSeed="" secondSeed="" winnerSeed=""/>
<result round="3" firstSeed="" secondSeed="" winnerSeed=""/>
<result round="4" firstSeed="" secondSeed="" winnerSeed=""/>
</results>
</bracket> |
Listing 15는 첫 번째 라운드 후에 토너먼트의 상태를 나타낸다. 라운드 2, 3, 4의 모든 <result> 엘리먼트들은 빈 winnerSeed 애트리뷰트를 갖고 있다. 그럼에도 불구하고, 스타일시트는 정확한 브래킷을 만들어 낸다. (그림 3)
그림 3. 불완전한 토너먼트에 대한 브래킷
요약
이 글에서, XPath 2.0과 XSLT 2.0의 새로운 기능을 살펴보았다. 샘플 애플리케이션에서, 볼품 없는 스타일시트를 가져다가 리팩토링 하여 보다 작고 관리하기 편리한 코드로 만들어 보았다. 코드를 분석하여 패턴을 찾을 수 있는데, to 연산자, <xsl:function>>, 아이템의 시퀀스 없이는, 스타일시트를 체계화 하기 어렵다.
가장 중요한 것은, 어떤 수의 경쟁자라도 핸들할 수 있는 제너릭 함수를 만들었다는 점이다. 스타일시트를 수정하여 32-팀 토너먼트를 핸들하려면, $seeds와 $periods 시퀀스를 선정해야 한다. 또한, select="1 to
5"를 select="1 to $rounds"로 대체해야 하는데, 여기에서 $rounds는 토너먼트의 라운드 수를 나타낸다. 물론 가장 훌륭한 솔루션은, 라운드의 수, 시드의 시퀀스(32-팀 브래킷은 1과 32, 2와 31 등의 매치업으로 구성된다.), 다양한 컬럼들의 피리어드를 포함하여, 제너릭 브래킷에 대한 값을 계산하는 XSLT 2.0 함수를 생성하는 것이다. 독자 여러분들이 직접 실습해 보기 바란다.
이 문제의 핵심은 테이블의 32 행들을 반복해야 한다는 것이지만, XSLT 1.0은 이를 해결할 수 있는 어떤 방법도 제공하지 못한다. XPath 2.0과 XSLT 2.0의 새로운 기능들로 이러한 문제를 해결한다.
다운로드 하십시오 | 설명 | 이름 | 크기 | 다운로드 방식 |
|---|
| XML, XSLT, HTML 코드 샘플 | x-xslt20xpath20/samples.zip | 12KB | HTTP |
|---|
참고자료 교육
제품 및 기술 얻기
토론
필자소개  | 
|  | Doug Tidwell은 IBM의 Software Group Strategy 팀의 기술 전도사이다. 현재, Service Component Architecture (SCA), Service Data Objects (SDO), XForms 같은 신 기술에 집중하고 있다. O'Reilly의
XSLT
(현재 2판 인쇄)의 저자이다. 1997년 최초의 XML 컨퍼런스의 연사였으며, 20여년 동안 마크업 언어 분야에서 일해왔다. 현재, 영국 패스트푸드 업계의 거물 William "Add-a-Piece" Thackeray에 관한 책을 쓰고 있다. 노쓰 캐롤라이나, Chapel Hill에서 아내와 딸과 함께 살고 있다. |
기사에 대한 평가
|  |