GitBucket
4.21.2
Toggle navigation
Snippets
Sign in
Files
Branches
1
Releases
1
Issues
Pull requests
Labels
Priorities
Milestones
Wiki
Forks
nigel.stanger
/
XML
Browse code
- Complete rewrite to generate XSLT templates and functions for inclusion into
the authoring framework.
master
1 parent
e949c32
commit
d71caa7f661dc77c8334fbdb3a010a949874c22e
nstanger
authored
on 13 Jun 2012
Patch
Showing
1 changed file
generate_calendar_dates.php
Ignore Space
Show notes
View
generate_calendar_dates.php
<?php /* File: $Id$ This script generates an XSLT include file that contains templates specifying the date ranges for every week of the three main teaching periods through the year (i.e., summer school, first semester, second semester). Simply set the configuration as outlined below and make to regenerate the templates. */ /* Date of the Monday of the first academic week of the year. Teaching weeks start on Monday, so this is a convenient base point to start from. This will need to be updated annually. */ $first_monday = new DateTime( "2012-01-02" ); /* Teaching period configuration. This will need to be updated annually. It's easily extensible to a new teaching period simply by adding a new specification for that period. The key should be mixed-case alphanumeric (i.e., no whitespace, no punctuation or other special characters). The components of each period specification are: first_week: The academic week number of the first week of the period. last_week: The academic week number of the last week of the period. break_weeks: A list of breaks that occur during the teaching period. Each is specified by the start (break_start) and end (break_end) week. If a break is only one week long, then break_start and break_end should be equal. If there are no breaks in the teaching period, include a single break specification with both break_start and break_end set to zero. The list should ideally be in ascending order, but the script doesn't assume this and sorts the list anyway. Weeks are numbered by academic week number rather than week within the teaching period. */ $periods = array( 'SS' => array( 'first_week' => 2, 'last_week' => 7, 'break_weeks' => array( array( 'break_starts' => 0, 'break_ends' => 0, ), ), ), 'S1' => array( 'first_week' => 9, 'last_week' => 22, 'break_weeks' => array( array( 'break_starts' => 15, 'break_ends' => 15, ), ), ), 'S2' => array( 'first_week' => 28, 'last_week' => 41, 'break_weeks' => array( array( 'break_starts' => 35, 'break_ends' => 35, ), ), ), 'FY' => array( 'first_week' => 9, 'last_week' => 41, 'break_weeks' => array( array( 'break_starts' => 23, 'break_ends' => 27, ), array( 'break_starts' => 15, 'break_ends' => 15, ), array( 'break_starts' => 35, 'break_ends' => 35, ), ), ), ); // We need a condition to validate the period code in the templates. $period_condition = sprintf( "( @period = '%s' )", implode( "' ) or ( @period = '", array_keys( $periods ) ) ); // We also a list of valid period code values for the error string. $period_list = sprintf( '"%s"', implode( '", "', array_keys( $periods ) ) ); // The period and week variables are defined identically in both templates, so // let's define them just once here to ensure consistency. $shared_variables = <<<EOT <xsl:variable name="period"> <xsl:choose> <xsl:when test="{$period_condition}"> <xsl:value-of select="@period" /> </xsl:when> <!-- This also covers the case of @period being undefined. --> <xsl:otherwise> <xsl:message terminate="yes"> <xsl:text>Attribute "period" must be one of the values {$period_list}.</xsl:text> </xsl:message> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="week"> <xsl:value-of select="@week" /> <xsl:if test="not( @week )"> <xsl:message terminate="yes"> <xsl:text>The "week" attribute is required.</xsl:text> </xsl:message> </xsl:if> </xsl:variable> EOT; // Generate the file header and the wrapper templates. print<<<EOT <?xml version="1.0" encoding="utf-8"?> <!-- DO NOT EDIT! Automatically generated by ../generate_calendar_dates.php! Elements and functions for generating teaching period dates. Apart from the wrapper templates, this essentially boils down to a giant parameterised lookup table, which is generated by a script. --> <stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <!-- Generate a date for a given teaching week in a teaching period, in the specified format. Return the date of the Monday of the week, plus an optional signed offset. @period: The teaching period for which to generate the date. [required] @week: The number of the week in the specified teaching period. [required] @format: How to format the date. 'day+long-name' => [FNn] [D] [MNn] (e.g., Friday 10 August) 'day+short-name' => [FNn,*-3] [D] [MNn] (e.g., Fri 10 August) 'day' => [D] [MNn] (e.g., 10 August) 'ISO' => [Y0001]-[M01]-[D01] (e.g., 2012-08-10) date picture string => Any valid XSLT date picture specification (see http://www.w3.org/TR/xslt20/#date-picture-string). @offset: A signed offset specified as an XSLT duration, e.g., "P1D" for plus one day, "-P4D" for minus four days. --> <template name="TeachingPeriodDate" match="TeachingPeriodDate"> <common> {$shared_variables} <xsl:variable name="format"> <xsl:choose> <!-- May add some specific simple-date formats here. --> <xsl:when test="@format = 'day+long-name'"> <xsl:text>[FNn] [D] [MNn]</xsl:text> </xsl:when> <xsl:when test="@format = 'day+short-name'"> <xsl:text>[FNn,*-3] [D] [MNn]</xsl:text> </xsl:when> <xsl:when test="@format = 'day'"> <xsl:text>[D] [MNn]</xsl:text> </xsl:when> <xsl:when test="@format = 'ISO'"> <xsl:text>[Y0001]-[M01]-[D01]</xsl:text> </xsl:when> <xsl:otherwise> <xsl:value-of select="@format" /> </xsl:otherwise> </xsl:choose> <xsl:if test="not( @format )"> <xsl:text>[Y0001]-[M01]-[D01]</xsl:text> </xsl:if> </xsl:variable> <xsl:variable name="offset"> <xsl:value-of select="@offset" /> <xsl:if test="not( @offset )"> <xsl:text>P0D</xsl:text> </xsl:if> </xsl:variable> <xsl:value-of select="infosci:format-teaching-date( \$period, \$week, \$format, \$offset )" /> </common> </template> <!-- Generate a date range corresponding to a given teaching week in a teaching period (i.e., Monday to Friday). @period: The teaching period for which to generate the date. [required] @week: The number of the week in the specified teaching period. [required] @format: How to format the month name in the date range. 'long' => Output the full month name (e.g., August) 'short' => Output only the first three characters of the month name (e.g., Aug) @wrap: Whether to wrap the date range across two lines (usually for insertion into a narrow cell in a calendar table). 'true' [default] 'false' --> <template name="TeachingPeriodDateRange" match="TeachingPeriodDateRange"> <common> {$shared_variables} <xsl:variable name="month-format"> <xsl:choose> <xsl:when test="@format = 'long'"> <xsl:text>[MNn]</xsl:text> </xsl:when> <xsl:when test="@format = 'short'"> <xsl:text>[MNn,*-3]</xsl:text> </xsl:when> <xsl:otherwise> <xsl:message terminate="yes"> <xsl:text>Attribute "format" must be either "long" or "short", not "</xsl:text> <xsl:value-of select="@format" /> <xsl:text>".</xsl:text> </xsl:message> </xsl:otherwise> </xsl:choose> </xsl:variable> <!-- Convert @wrap into a boolean value. --> <xsl:variable name="wrap"> <xsl:value-of select="@wrap = 'yes'" /> </xsl:variable> <xsl:value-of select="infosci:format-teaching-date-range( \$period, \$week, \$month-format, \$wrap )" /> </common> </template> EOT; // Generate the XSLT functions that do all the work. These essentially end up being huge parameterised lookup tables. generate_function( $first_monday, $periods, 'format-teaching-date' ); generate_function( $first_monday, $periods, 'format-teaching-date-range' ); // Footer boilerplate. print( "</stylesheet>\n" ); exit; //////////////////////////////////////////////////////////////////////////////// function generate_function( $first_monday, $periods, $function_name ) { // A couple of handy date intervals: four days to the end of the current week, // and seven days to the start of next week. $plus_four_days = new DateInterval( 'P4D' ); $plus_seven_days = new DateInterval( 'P7D' ); // Initial boilerplate for the function. switch ( $function_name ) { case 'format-teaching-date': print<<<EOT <!-- Generate a date (default Monday) for the specified teaching period and week (or break), using the specified date format, with an optional offset from the Monday of the week. Most of the body of this function is automatically generated by the script ../generate_calendar_dates.php. See the attributes of the TeachingPeriodDate template above for descriptions of the parameters. --> <function name="infosci:format-teaching-date"> <common> <xsl:param name="period" as="xs:string" /> <xsl:param name="week" as="xs:string" /> <xsl:param name="format" as="xs:string" /> <xsl:param name="offset" as="xs:dayTimeDuration" /> <xsl:choose> EOT; break; case 'format-teaching-date-range': print<<<EOT <!-- Generate a date range for the specified teaching period and week (or break), using the specified month format, with optional wrapping. Most of the body of this function is automatically generated by the script ../generate_calendar_dates.php. See the attributes of the TeachingPeriodDateRange template above for descriptions of the parameters. \$month-format is derived from @format. --> <function name="infosci:format-teaching-date-range"> <common> <xsl:param name="period" as="xs:string" /> <xsl:param name="week" as="xs:string" /> <xsl:param name="month-format" as="xs:string" /> <xsl:param name="wrap" as="xs:boolean" /> <xsl:choose> EOT; break; default: // WTF?!? print "Unrecognised function name \"$function_name\", terminating.\n"; exit; break; } // switch function name // Generate XSLT code for each teaching period. foreach ( $periods as $period_name => $period_data ) { print<<<EOT <xsl:when test="\$period = '{$period_name}'"> <xsl:choose> EOT; $week_start = clone( $first_monday ); // Jump forward the correct number of weeks to the start of the teaching period. $week_start->add( new DateInterval( sprintf( "P%dD", ( $period_data['first_week'] - 1 ) * 7 ) ) ); $week_number = 0; $num_breaks = 0; // Grab the first break week off the front of the list. sort( $period_data['break_weeks'] ); $break_week = array_shift( $period_data['break_weeks'] ); // Generate XSLT code for each teaching week and breaks within the period. for ( $week = $period_data['first_week']; $week <= $period_data['last_week']; $week++ ) { $week_end = clone( $week_start ); $week_end->add( $plus_four_days ); // Handle breaks. if ( ( $week >= $break_week['break_starts'] ) && ( $week <= $break_week['break_ends'] ) ) { if ( $week == $break_week['break_starts'] ) { $num_breaks++; // Work out the end date of the break. $break_end = clone( $week_start ); // Note: +4 days to get to Friday. $break_end->add( new DateInterval( sprintf( "P%dD", ( ( $break_week['break_ends'] - $break_week['break_starts'] ) * 7 ) + 4 ) ) ); switch ( $function_name ) { case 'format-teaching-date': generate_date( "B{$num_breaks}", $week_start ); break; case 'format-teaching-date-range': if ( $week_start->format( 'n' ) == $break_end->format( 'n' ) ) { generate_same_month_date_range( "B{$num_breaks}", $week_start, $break_end ); } // if break start and break end are in the same month else { generate_different_month_date_range( "B{$num_breaks}", $week_start, $break_end ); } // else break start and break end are in different months break; default: // WTF?!? print "Unrecognised function name \"$function_name\", terminating.\n"; exit; break; } // switch function name } // if current week == first week of break if ( $week == $break_week['break_ends'] ) { // Grab the next break week off the front of the list. $break_week = array_shift( $period_data['break_weeks'] ); } // if current week == last week of break } // if current week is a break week else { $week_number++; switch ( $function_name ) { case 'format-teaching-date': generate_date( $week_number, $week_start ); break; case 'format-teaching-date-range': if ( $week_start->format( 'n' ) == $week_end->format( 'n' ) ) { generate_same_month_date_range( $week_number, $week_start, $week_end ); } // if break start and break end are in the same month else { generate_different_month_date_range( $week_number, $week_start, $week_end ); } // else break start and break end are in different months break; default: // WTF?!? print "Unrecognised function name \"$function_name\", terminating.\n"; exit; break; } // switch function name } // else normal teaching week $week_start->add( $plus_seven_days ); } // for each week // Terminating boilerplate for this teaching period. print<<<EOT <xsl:otherwise> <xsl:message terminate="yes"> <xsl:text>Invalid {$period_name} week specification "</xsl:text> <xsl:value-of select="\$week" /> <xsl:text>".</xsl:text> </xsl:message> </xsl:otherwise> </xsl:choose> </xsl:when> EOT; } // foreach teaching period // Terminating boilerplate for the function. print<<<EOT </xsl:choose> </common> </function> EOT; } // generate_function //////////////////////////////////////////////////////////////////////////////// function generate_date( $week_spec, $date ) { print<<<EOT <xsl:when test="\$week = '{$week_spec}'"> <xsl:value-of select="format-date( xs:date( '{$date->format( 'Y-m-d' )}' ) + \$offset, \$format )" /> </xsl:when> EOT; } // generate_same_month_date_range //////////////////////////////////////////////////////////////////////////////// function generate_same_month_date_range( $week_spec, $start_date, $end_date ) { print<<<EOT <xsl:when test="\$week = '{$week_spec}'"> <xsl:value-of select="format-date( xs:date( '{$start_date->format( 'Y-m-d' )}' ), '[D]' )" /> <xsl:call-template name="endash" /> <xsl:value-of select="format-date( xs:date( '{$end_date->format( 'Y-m-d' )}' ), '[D] ' )" /> <xsl:if test="\$wrap"> <xsl:call-template name="newline" /> </xsl:if> <xsl:value-of select="format-date( xs:date( '{$end_date->format( 'Y-m-d' )}' ), \$month-format )" /> </xsl:when> EOT; } // generate_same_month_date_range function generate_different_month_date_range( $week_spec, $start_date, $end_date ) { print<<<EOT <xsl:when test="\$week = '{$week_spec}'"> <xsl:value-of select="format-date( xs:date( '{$start_date->format( 'Y-m-d' )}' ), concat( '[D] ', \$month-format ) )" /> <xsl:if test="\$wrap"> <xsl:call-template name="newline" /> </xsl:if> <xsl:call-template name="endash" /> <xsl:value-of select="format-date( xs:date( '{$end_date->format( 'Y-m-d' )}' ), concat( '[D] ', \$month-format ) )" /> </xsl:when> EOT; } // generate_different_month_date_range ?>
<?php /* File: $Id$ This script generates an XSLT include file that contains templates specifying the date ranges for every week of the three main teaching periods through the year (i.e., summer school, first semester, second semester). Simply set the configuration as outlined below and make to regenerate the templates. */ /* Teaching period configuration. It's easily extensible to a new teaching period simply by adding a new specification for that period. The key should be mixed-case alphanumeric (i.e., no whitespace, no punctuation or other special characters). The components of each period specification are: start_date: The date of the first day of the first week of the period. num_weeks: The total number of weeks in the period, /including/ any breaks (e.g., 13 teaching weeks plus 1 week mid-semester break = 14 weeks). break_weeks: A list of breaks that occur during the teaching period. Each is specified by the start (break_start) and end (break_end) week. If a break is only one week long, then break_start and break_end should be equal. If there are no breaks in the teaching period, include a single break specification with both break_start and break_end set to zero. The list should ideally be in ascending order, but the script doesn't assume this and sorts the list anyway. Weeks are numbered by absolute calendar week number (i.e., week 1 is the first calendar week of the year). The date of the first Monday of the year is stored in $first_monday below (everything is Monday-indexed). */ $first_monday = new DateTime( "2012-01-02" ); $periods = array( 'SummerSchool' => array( 'first_week' => 2, 'last_week' => 7, 'break_weeks' => array( array( 'break_starts' => 0, 'break_ends' => 0, ), ), ), 'SemesterOne' => array( 'first_week' => 9, 'last_week' => 22, 'break_weeks' => array( array( 'break_starts' => 15, 'break_ends' => 15, ), ), ), 'SemesterTwo' => array( 'first_week' => 28, 'last_week' => 41, 'break_weeks' => array( array( 'break_starts' => 35, 'break_ends' => 35, ), ), ), 'FullYear' => array( 'first_week' => 9, 'last_week' => 41, 'break_weeks' => array( array( 'break_starts' => 23, 'break_ends' => 27, ), array( 'break_starts' => 15, 'break_ends' => 15, ), array( 'break_starts' => 35, 'break_ends' => 35, ), ), ), ); // A couple of handy date intervals: four days to the end of the current week, // and seven days to the start of next week. $plus_four_days = new DateInterval( 'P4D' ); $plus_seven_days = new DateInterval( 'P7D' ); // Header boilerplate. print<<<EOT <?xml version="1.0" encoding="utf-8"?> <!-- Do not edit! Automatically generated by generate_calendar_dates.php! --> <stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <!-- General template for formatting teaching dates. --> EOT; // printf( " <template name=\"%sTeachingDate\" match=\"%sTeachingDate\">\n" ); // print( " <common>\n" ); // print( " <xsl:variable name=\"period-start\" as=\"xs:date\">\n" ); // print( " <xsl:if test=\"@period-start\">\n" ); // print( " <xsl:value-of select=\"@period-start\">\n" ); // print( " </xsl:if\n" ); // printf( " <xsl:text>%s</xsl:text> \n", ); // print( " </xsl:variable name=\"period-start\">\n" ); // <xsl:variable name="break-start" select="@break-start" /> // <xsl:variable name="period-start" select="@period-start" /> // <xsl:variable name="period-start" select="@period-start" /> // <xsl:value-of select="infosci:format-number( node() )" /> // </common> // </template> foreach ( $periods as $period_name => $period_data ) { $week_start = clone( $first_monday ); // Jump forward the correct number of weeks to the start of the teaching period. $week_start->add( new DateInterval( sprintf( "P%dD", ( $period_data['first_week'] - 1 ) * 7 ) ) ); $week_number = 0; $num_breaks = 0; // Grab the first break week off the front of the list. sort( $period_data['break_weeks'] ); $break_week = array_shift( $period_data['break_weeks'] ); for ( $week = $period_data['first_week']; $week <= $period_data['last_week']; $week++ ) { $week_end = clone( $week_start ); $week_end->add( $plus_four_days ); // Handle break weeks. if ( ( $week >= $break_week['break_starts'] ) && ( $week <= $break_week['break_ends'] ) ) { if ( $week == $break_week['break_starts'] ) { $num_breaks++; // Work out the end date of the break. $break_end = clone( $week_start ); // Note: +4 days to get to Friday. $break_end->add( new DateInterval( sprintf( "P%dD", ( ( $break_week['break_ends'] - $break_week['break_starts'] ) * 7 ) + 4 ) ) ); // Output template for inclusion in paper calendar. printf( "\t<xsl:template name=\"%sBreak%d\" match=\"%sBreak%d\">\n", $period_name, $num_breaks, $period_name, $num_breaks ); printf( "\t\t<xsl:text>%s</xsl:text>\n", // If the week start and end dates are in the same month, we only // need to output the month name once. ( $week_start->format( 'n' ) == $break_end->format( 'n' ) ) ? $week_start->format( 'j' ) : $week_start->format( 'j M' ) ); print( "\t\t<xsl:call-template name=\"endash\" />\n" ); printf( "\t\t<xsl:text%s</xsl:text>\n", $break_end->format( 'j M' ) ); print( "\t</xsl:template>\n\n" ); } // if current week == first week of break if ( $week == $break_week['break_ends'] ) { // Grab the next break week off the front of the list. $break_week = array_shift( $period_data['break_weeks'] ); } // if current week == last week of break } // if current week is a break week else { $week_number++; // Output template for inclusion in paper calendar. printf( "\t<xsl:template name=\"%sWeek%d\" match=\"%sWeek%d\">\n", $period_name, $week_number, $period_name, $week_number ); // If the week start and end dates are in the same month, we only // need to output the month name once. if ( $week_start->format( 'n' ) == $week_end->format( 'n' ) ) { printf( "\t\t<xsl:text>%s</xsl:text>\n", $week_start->format( 'j' ) ); print( "\t\t<xsl:call-template name=\"endash\" />\n" ); printf( "\t\t<xsl:text>%s</xsl:text>\n", $week_end->format( 'j' ) ); print( "\t\t<xsl:call-template name=\"newline\" />\n" ); printf( "\t\t<xsl:text>%s</xsl:text>\n", $week_end->format( 'M' ) ); } // if same month else { printf( "\t\t<xsl:text>%s</xsl:text>\n", $week_start->format( 'j M' ) ); print( "\t\t<xsl:call-template name=\"endash\" />\n" ); printf( "\t\t<xsl:text>%s</xsl:text>\n", $week_end->format( 'j M' ) ); } // else different months printf( "\t</xsl:template>\n\n" ); } // else normal teaching week $week_start->add( $plus_seven_days ); } } // Footer boilerplate. print( "</xsl:stylesheet>\n" ); ?>
Show line notes below