UPDATE: В связи с ошибками содержащимися в предыдущем варианте этого примера, пример был серьёзно переделан и вместо двух неправильных вариантов кода, приведён один, — правильный.

Первое, с чего хочу начать, — это собственно XML, формируемый парсером. Он имеет следующий вид (ноябрь 2002):

<month>
	<month_year
		month="11"
		month_title="ноябрь"
		next_month="12"
		prev_month="10"
		year="2002"
		next_year="2003"
		prev_year="2001"
		hit="1"
	/>

	<weekdays>
		<day title="Пн"/>
		<day title="Вт"/>
		<day title="Ср"/>
		<day title="Чт"/>
		<day title="Пт"/>
		<day title="Сб"/>
		<day title="Вс"/>
	</weekdays>

	<week>
		<day d="0"/>
		<day d="0"/>
		<day d="0"/>
		<day d="0"/>
		<day d="1"/>
		<day d="2"/>
		<day d="3" f="1"/>
	</week>

	<week>
		<day d="4"/>
		<day d="5"/>
		<day d="6" f="1" hit="1"/>
		<day d="7"/>
		<day d="8"/>
		<day d="9"/>
		<day d="10" in="1"/>
	</week>
	...
</month>

Или графически (сгенерировано XML Spy ) это будет выглядеть так:

Структура xml календаря

Думаю особо объяснять тут нечего, расскажу лишь о назначении некоторых атрибутов. in — появляется у дня если этот день является текущим днем, f — появляется у дня, за который были материалы, hit — этот день/месяц выбран пользователем. Да и вы наверно уже обратили внимание что элементов week может быть несколько, сколько? — информация к размышлению.

Информация о том, за какой месяц выводить календарь передаются коду класса через параметр конструктора. Параметр должен быть хэшем следующей структуры:

$date_user[
	$.day(2)
	$.month(10)
	$.year(2002)
]

Т.е. если данные берутся из строки запроса, хэш можно инициализировать следующим образом:

$date_user[
	$.day(^form:d.int(0))
	$.month(^form:m.int(0))
	$.year(^form:y.int(0))
]

Если при создании экземпляра класса ему передаётся хэш с нулевыми ключами, выводится календарь на текущий месяц текущего года.

Далее код класса:

#######
# Calendar class
@CLASS
calendar

#######
@init[date_user]
# получение текущей даты
$date_now[^date::now[]]

^if($date_user.year && $date_user.month){
	$d($date_user.day)
	$m($date_user.month)
	$y($date_user.year)
}{
	$d($date_now.day)
	$m($date_now.month)
	$y($date_now.year)
}

# Инициал. хэш показывающий какой период времени передан
# при создании экземпляра класса
$self.date_user[$date_user]

# вызов метода для создания специфических для языка переменных
# по-умолчанию для русского, если нужен другой язык, сделать
# класс производный от этого и переопределить в нем только один
# метод @i18n[]
^i18n[]

# определение переменных месяца и года для пред. месяца календаря
$calendar_month_prev[^date::create($y;$m)]
^calendar_month_prev.roll[month](-1)
$prev_m($calendar_month_prev.month)
$prev_y($calendar_month_prev.year)

# определение переменных месяца и года для след. месяца календаря
$calendar_month_next[^date::create($y;$m)]
^calendar_month_next.roll[month](+1)
$next_m($calendar_month_next.month)
$next_y($calendar_month_next.year)

# the hash of publications(files)
$days_of_files[
	^hash::sql{
		SELECT
			DAYOFMONTH(date_field),
			some_field
		FROM
			some_table
		WHERE
			MONTH(date_field) = $m and
			YEAR(date_field) = $y
	}[$.distinct(1)]
]

#######
# Метод, в котором определяются специфические для разных языков
# параметры календаря
@i18n[]
# the hash of local calendar (month and day names)
$calendar_locale[ 
	$.month_names[ 
		$.1[Январь]
		$.2[Февраль]
		$.3[Март]
		$.4[Апрель]
		$.5[Май]
		$.6[Июнь]
		$.7[Июль]
		$.8[Август]
		$.9[Сентябрь]
		$.10[Октябрь]
		$.11[Ноябрь]
		$.12[Декабрь]
	]
	$.day_names[
		$.0[Пн]
		$.1[Вт]
		$.2[Ср]
		$.3[Чт]
		$.4[Пт]
		$.5[Сб]
		$.6[Вс]
	]
]
$month[$calendar_locale.month_names.$m]
$calendar_month[^date:calendar[rus]($y;$m)] 

#######
@xml[]
$result[
	<month>
		^month_year[]
		^weekdays[]
		^week[]
	</month>
]

#######
# формирование тега для года и месяца
@month_year[][hit]
^if(
	$date_user.day ||
	(
		$date_now.month == $date_user.month && 
		$date_now.year == $date_user.year &&
		$date_user.day
	)
){
	$hit[]
}{
	$hit[hit="1"]
}

<month_year
	month="$m"
	month_title="$month"
	next_month="$next_m"
	prev_month="$prev_m"
	year="$y"
	next_year="$next_y"
	prev_year="$prev_y"
	$hit
/>

#######
# тег названий дней недели
@weekdays[]
<weekdays>
^for[i](0;6){
	<day title="$calendar_locale.day_names.$i"/>
}
</weekdays>

#######
# формирование тегов для каждой недели месяца
# day - день месяца, может быть нулем, если начало/конец
# первой/последней недели месяца не приходятся на понедельник/воскресенье
@week[][day;in;f;hit]
^calendar_month.menu{
	<week>
	^for[i](0;6){
		$day($calendar_month.$i)
# формирование атрибута текущего дня
		^if(
			$date_now.day == $day &&
			$date_now.month == $date.month &&
			$date_now.year == $date.year
		){
			$in[in="1"]
		}{
			$in[]
		}
# формирование атрибута нахождения в данном дне
		^if($day == $date.day){
			$hit[hit="1"]
		}{
			$hit[]
		}
# формирование атрибута наличия материалов за день
		^if($days_of_files.$day){
			$f[f="1"]
		}{
			$f[]
		}
		<day d="$day" $in $f $hit/>
	}
	</week>
}

Для получения XML кода календаря нужно сформировать объект конструктором init $object[^calendar::init[$date_user]] далее вызовом ^object.xml[] получаем XML календаря.

Преобразуем полученный XML в HTML:

$date_user[
	$.day(^form:d.int(0))
	$.month(^form:m.int(0))
	$.year(^form:y.int(0))
]

$object[^calendar::init[$date_user]]

$calendar_xml[^xdoc::create{^object.xml[]}]

$calendar_html[^$calendar_xml.transform[calendar.xsl]]

^calendar_html.string[
	$.method[html]
	$.indent[no]
]

Пример стилевой таблицы calendar.xsl:

<?xml version="1.0" encoding="windows-1251"?>

<xsl:stylesheet
	version="1.0"
	xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>

<!-- переменные начальных значений месяца и года календаря -->

	<xsl:variable name="start_year">2002</xsl:variable>
	<xsl:variable name="start_month">2</xsl:variable>

	<xsl:template match="month">
		<table cellpadding="4" cellspacing="3" width="100%">
			<tr>
				<td
					bgcolor="white"
					align="center"
					width="16%"
				>
					<xsl:call-template name="prev_month"/>
				</td>

				<td
					bgcolor="white"
					align="center"
					colspan="5"
					width="68%"
				>
					<xsl:choose>
						<xsl:when test="week/*/@in">
							<strong>
							<xsl:call-template name="month_year_link"/>
							</strong>
						</xsl:when>
						<xsl:otherwise>
							<xsl:call-template name="month_year_link"/>
						</xsl:otherwise>
					</xsl:choose>

				</td>

				<td bgcolor="" align="center" width="16%">
					<xsl:call-template name="next_month"/>
				</td>
			</tr>

			<tr>
				<xsl:apply-templates select="weekdays"/>
			</tr>

			<xsl:for-each select="week">
				<tr>
					<xsl:call-template name="week"/>
				</tr>
			</xsl:for-each>

		</table>
	</xsl:template>

<!-- ссылка на предыдущий месяц -->

	<xsl:template name="prev_month">
		<xsl:choose>
			<xsl:when
				test="
					month_year/@prev_year &lt; $start_year
				"
			>
				&lt;&lt;
			</xsl:when>

			<xsl:otherwise>
				<xsl:choose>
					<xsl:when
						test="
							month_year/@prev_month &lt; $start_month and 
							month_year/@prev_year = $start_year
						"
					>
						&lt;&lt;
					</xsl:when>
					<xsl:otherwise>
						<a
							href="{month_year/@prev_year}-{month_year/@prev_month}.html"
							style="text-decoration: none;"
						>
							&lt;&lt;
						</a>
					</xsl:otherwise>
				</xsl:choose>
			</xsl:otherwise>
		</xsl:choose>
	</xsl:template>

<!-- ссылка на следующий месяц -->

	<xsl:template name="next_month">
		<xsl:choose>
			<xsl:when test="week/*/@in">
				&gt;&gt;
			</xsl:when>

			<xsl:otherwise>
				<a
					href="{month_year/@next_year}-{month_year/@next_month}.html"
					style="text-decoration: none;"
				>
					&gt;&gt;
				</a>
			</xsl:otherwise>
		</xsl:choose>
	</xsl:template>

<!-- ссылка на текущий месяц -->

	<xsl:template name="month_year_link">
		<xsl:choose>
			<xsl:when test="month_year/@hit">
				<xsl:call-template name="month_year"/>
			</xsl:when>
			
			<xsl:otherwise>
				<a href="{month_year/@year}-{month_year/@month}.html">
					<xsl:call-template name="month_year"/>
				</a>
			</xsl:otherwise>
		</xsl:choose>
	</xsl:template>

<!-- название месяца и года -->

	<xsl:template name="month_year">
		<xsl:value-of select="month_year/@month_title"/>
		<xsl:text> </xsl:text>
		<xsl:value-of select="month_year/@year"/>
	</xsl:template>

<!-- названия дней недели -->

	<xsl:template match="weekdays">
		<xsl:for-each select="day">
			<td
				align="center"
				width="16%"
				bgcolor="#eeeeee"
			>
				<xsl:choose>
					<xsl:when test="position() != 7">
						<xsl:value-of select="@title"/>
					</xsl:when>
					<xsl:otherwise>
						<span style="color: red">
							<xsl:value-of select="@title"/>
						</span>
					</xsl:otherwise>
				</xsl:choose>
			</td>
		</xsl:for-each>
	</xsl:template>

<!-- вывод недель месяца -->

	<xsl:template name="week">
		<xsl:for-each select="day">
			<td align="center" width="16%">
				<xsl:attribute name="bgcolor">
					<xsl:choose>
						<xsl:when test="@in">
							#ffcc00
						</xsl:when>
						<xsl:otherwise>
							white
						</xsl:otherwise>
					</xsl:choose>
				</xsl:attribute>

				<xsl:choose>
					<xsl:when test="position() != 7">
						<xsl:call-template name="day"/>
					</xsl:when>
					<xsl:otherwise>
						<span style="color: red">
							<xsl:call-template name="day"/>
						</span>
					</xsl:otherwise>
				</xsl:choose>
			</td>
		</xsl:for-each>
	</xsl:template>

<!-- вывод дней -->

	<xsl:template name="day">

		<xsl:choose>
			<xsl:when test="@hit">
				<strong><xsl:call-template name="day_value"/></strong>
			</xsl:when>

			<xsl:otherwise>
				<xsl:call-template name="no_hit"/>
			</xsl:otherwise>
		</xsl:choose>

	</xsl:template>

<!-- вывод невыбранных дней -->

	<xsl:template name="no_hit">

		<xsl:choose>
			<xsl:when test="@f">
				<a href="{//*/@year}-{//*/@month}-{@d}.html">
					<xsl:call-template name="day_value"/>
				</a>
			</xsl:when>

			<xsl:otherwise>
				<xsl:call-template name="day_value"/>
			</xsl:otherwise>
		</xsl:choose>

	</xsl:template>

	<xsl:template name="day_value">

		<xsl:choose>
			<xsl:when test="@d = 0"/>

			<xsl:when test="@d > 9">
				<xsl:value-of select="@d"/>
			</xsl:when>

			<xsl:otherwise>
				<xsl:text>0</xsl:text><xsl:value-of select="@d"/>
			</xsl:otherwise>
		</xsl:choose>

	</xsl:template>

</xsl:stylesheet>

Этот календарь легко преобразуется в требуемый вид для другого языка, например английского. Для этого необходимо создать класс производный от класса calendar в котором переопределяется всего лишь один метод ^i18n[], например для английского языка:

#######
@CLASS
calendar_eng

#######
@USE
calendar.p

#######
@BASE
calendar

#######
# Метод, в котором определяются специфические для разных языков
# параметры календаря
@i18n[]
# the hash of local calendar (month and day names)
$calendar_locale[ 
	$.month_names[ 
		$.1[January]
		$.2[February]
		$.3[March]
		$.4[April]
		$.5[May]
		$.6[June]
		$.7[July]
		$.8[August]
		$.9[September]
		$.10[October]
		$.11[November]
		$.12[December]
	]
	$.day_names[
		$.0[Su]
		$.1[Mo]
		$.2[Tu]
		$.3[We]
		$.4[Th]
		$.5[Fr]
		$.6[Sa]
	]
]
$month[$calendar_locale.month_names.$m]
$calendar_month[^date:calendar[eng]($y;$m)] 

Ну и далее вместо класса calendar подключайте и используйте calendar_eng:

$object[^calendar_eng::init[]]
$calendar_xml[<?xml version="1.0" encoding="$request:charset"?>
^xdoc::create{^object.xml[]}]
$calendar_html[^$calendar_xml.transform[calendar_eng.xsl]]
^calendar_html.string[
	$.method[html]
	$.indent[no]
]

Да и ещё, — в английском языке первый день недели не понедельник а воскресенье, поэтому стилевую таблицу необходимо подправить(переопределить в ней пару шаблонов). Текст calendar_eng.xsl:

<?xml version="1.0" encoding="windows-1251"?>

<xsl:stylesheet
	version="1.0"
	xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
	<xsl:import href="calendar.xsl"/>

	<xsl:template match="weekdays">
		<xsl:for-each select="day">
			<td
				bgcolor="#eeeeee"
				align="center"
				width="16%"
			>
				<xsl:choose>
					<xsl:when test="position() != 1">
						<xsl:value-of select="@title"/>
					</xsl:when>
					<xsl:otherwise>
						<span style="color: red">
							<xsl:value-of select="@title"/>
						</span>
					</xsl:otherwise>
				</xsl:choose>
			</td>
		</xsl:for-each>
	</xsl:template>

	<xsl:template name="week">
		<xsl:for-each select="day">
			<td align="center" width="16%">
				<xsl:attribute name="bgcolor">
					<xsl:choose>
						<xsl:when test="@in">
							#ffcc00
						</xsl:when>
						<xsl:otherwise>
							white
						</xsl:otherwise>
					</xsl:choose>
				</xsl:attribute>

				<xsl:choose>
					<xsl:when test="position() != 1">
						<xsl:call-template name="day"/>
					</xsl:when>
					<xsl:otherwise>
						<span style="color: red">
							<xsl:call-template name="day"/>
						</span>
					</xsl:otherwise>
				</xsl:choose>
			</td>
		</xsl:for-each>
	</xsl:template>

</xsl:stylesheet>

Разумеется можно и не получать HTML из XML не отходя от кассы, — в случае если полностью вся страница/сайт формируется сначала в XML'е и затем преобразуется в HTML, это делать совсем не надо. Надо просто вставить вызов ^object.xml[] в требуемое место, ну и затем в нужном xsl файле подключать calendar.xsl и ловить им элемент month в требуемом месте.

Ну вот и всё, ничего сложного я надеюсь. Работающий пример смотрите … сами знаете где смотреть :).

Загрузить пример calendar.zip

2002-11-21 17:07:11 UTC parser snippet web xml xslt