1: #!/bin/ksh
2: #
3: # $Header: /afs/northstar/ufac/richard/projects/class-web-builder/RCS/buildhtml,v 1.17 2005/03/19 05:15:48 richard Exp $
4: #
5: # Replace some metatag shortcuts with HTML tags, or
6: # redefine some standard tags with what we'd really
7: # like them to do.
8: #
9: # Arguments are expected to be 1 or more .src files, which are processed and left in
10: # the corresponding .html file. If no file names are specified, work with stdin/stdout.
11: # If an argument is a .html, but the corresponding .src file exists, that will be referenced
12: # instead. This allows simplification of the Makefiles - only one list of files is needed.
13: #
14: # Use the slide.order file to determine the section ordering and previous/next links
15: # for the navigation buttons.
16: #
17: # If source files are named, and contain DETAIL metatags, we also create .detail.html files
18: # and appropriate links to them.
19: #
20: # Not much error checking yet - assumes the slide.order file exists.
21: # All sed commands use # as regex delimiters since there are so many "/" in the variables
22: #
23: # This script is used in conjunction with buildslidelist and buildframeset
24: #
25: # 2001/10/26 Richard Brittain, Dartmouth College.
26: #
27: # 2003/10/27 RB Added support for <MCODE> translation - like <CODE> but with a link to
28: # online man pages (for introducing new commands). Changed some of the sed script regex characters to !
29: # since # may appear in the replacement strings (as colour specification)
30: # Added creation of named anchors for all H[1-3] tags, for use by a more sophisticated indexer.
31: # Added buildhtml.conf file option
32: #
33: # 2004/04/22 RB Allow for .shtml as well as .html
34: # 2004/10/19 RB Added option to customize DETAIL tags with fixed entry and exit codes. Apply these to both
35: # screen and printable format files
36: # 2005/02/03 RB Added css code to make page breaks for each new section in the printed output
37: # Omit the slide number at top right of each slide - repeat of navbar just above.
38: # 2005/02/09 RB Rewrite to use a single navigation plus content page and no more frameset and nav files.
39: # Frameless version works much better.
40: # 2005/03/10 RB Moved the "top" anchor from the very top to the start of the real content - makes
41: # class presentations flow much smoother. Nav buttons at bottom now point to the #top
42: # anchor, while the top ones just point to the bare page.
43: # Allow for case-independant HTML tags in the input files, and create only lower case tags
44: # in output.
45: # 2005/03/17 RB Make intro page a variable - not hard-coded "welcome.html"
46:
47: # Solaris has two versions of grep - make sure we get the right one by tweeking $PATH
48: PATH=/usr/xpg4/bin:$PATH; export PATH
49:
50: # Make sure we get these only from the .conf file
51: unset author keywords description
52:
53: # Read in the config file setting various optional features. Look only in the current directory
54: # This can preset several variables used later in this script.
55: [[ -r ./buildhtml.conf ]] && . ./buildhtml.conf
56:
57: edit_file()
58: {
59: # Use stdin,stdout. Let the caller redirect these as needed
60: # Uses global variable $mtime (set by caller)
61: # Uses preset environment variables for the HTML tag replacements, or hardwired defaults
62: # defined here.
63:
64: # $1 is the name of the source file
65: # $2 is a file ID (page number), to be placed somewhere on the page by these
66: # editing operations. The string will cause trouble if it confuses the 'sed' parsing.
67: # $3 is a filename for the "previous" link
68: # $4 is a filename for the "next" link
69: # $5 is a page title
70: src=$1
71: fid=$2
72: prevf=$3
73: nextf=$4
74: title=$5
75:
76: # HTML replacements
77: # Look for variables optionally set in buildhtml.conf, or in pre-existing
78: # environment variables.
79: # code_start, code_end, body_fn, h1_start, h1_end, leftarrow, rightarrow, uparrow
80: # man_url, vspace, h2_start, h2_end, h3_start, h3_end
81:
82: # <CODE> => Example Code
83: code_start="${code_start:-<code>}"
84: code_end="${code_end:-</code>}"
85:
86: # URL to use for online man pages (<MCODE> tags). Pattern \1 is the tag content string
87: # If not defined, process as for normal <CODE> tags
88: if [[ -z "$man_url" ]]; then
89: mancode=$code_start\\1$code_end
90: else
91: mancode='<a href='$man_url' target=manpage>'$code_start\\1$code_end'</a>'
92: fi
93:
94: # Default font> (screen only)
95: # Use a larger font than normal since mostly this is for class display
96: body_fn="${body_fn:-size=+1}"
97:
98: # Body start - default top material
99: # try to open a new window.
100: leftarrow=${leftarrow:-left.gif}
101: rightarrow=${rightarrow:-right.gif}
102: uparrow=${uparrow:-up.gif}
103:
104: # Header generation.
105: bodystart='<body '"$bodytag"'>'"\\
106: "'<font '"$body_fn"'>'"\\
107: "
108:
109: # <H[12]> redefinition
110: h1_start="${h1_start:-<h1>}"
111: h1_end="${h1_end:-</h1>}"
112: h2_start="${h2_start:-<h2>}"
113: h2_end="${h2_end:-</h2>}"
114: h3_start="${h3_start:-<h3>}"
115: h3_end="${h3_end:-</h3>}"
116:
117: # Vertical space at end of each page
118: vspace=${vspace:-"<br>"}
119:
120: # Header and Footer navigation links
121:
122: if [[ -n "$prevf" ]]; then
123: # Header generation - top navigation links.
124: # Note the complicated quoting and newlines are to get escaped newlines
125: # into the substitute string for sed, so that the generated HTML is a bit easier to read.
126: # Add in a page title and navigation links.
127: # Skip the TARGET="mainplusnav" - framed pages will load in the current frame, and frameless pages will not
128:
129: topnavlinks='<table width=100%><tr>'"\\
130: "' <td align="left"><font size=-1><a href="/cgi-bin/betsie.cgi">Text-only</a></font></td>'"\\
131: "' <td align="right"><font size=-1>Table of Contents (<a href="index.html" target=_top>frame</a>/'"\\
132: "'<a href="slide_list_noframes.html" target="_top">no frame</a>)</font></td>'"\\
133: "' </tr><tr>'"\\
134: "' <td align=LEFT>'"$fid $title"'</td>'"\\
135: "' <td align=RIGHT>'"\\
136: "' <a href="'$prevf'"><img src="'$leftarrow'" border=0 alt="Previous"></a>'"\\
137: "'<a href=\#top><img src="'"$uparrow"'" border=0 alt="Top"></a> '"\\
138: "' <a href="'$nextf'"><img src="'$rightarrow'" border=0 alt="Next"></a>'"\\
139: "' </td>'"\\
140: "'</tr></table>'"\\
141: "'<a name="top"></a>'"\\
142: "
143:
144: # Footer generation.
145: # For Frameless pages, skip the TARGET=mainplusnav in the nav links
146: botnavlinks="\\
147: "'<p align=CENTER>'"\\
148: "'<a href='"$prevf\#top"'><img src="'"$leftarrow"'" border=0 alt="Previous"></a> '"\\
149: "'<a href=\#top><img src="'"$uparrow"'" border=0 alt="Top"></a> '"\\
150: "'<a href='"$nextf\#top"'><img src="'"$rightarrow"'" border=0 alt="Next"></a>'"\\
151: "
152: # Alternate navigation links with text instead of icons.
153: # botnavlinks='<p align=CENTER><a href='"$prevf"'><i>Previous</i></a> <a href=\#top><i>Top</i></a> <a href='"$nextf"'"><i>Next</a></i>'
154: else
155: topnavlinks=
156: botnavlinks=
157: fi
158:
159: # More footer lines - include a call to the table of contents (slide list) for frames/noframes versions
160: # plus the printable version
161: lastmod="\\
162: "'<table width=100%><tr>'"\\
163: "'<td align="left" width=20% ><font size=-1>'"$src"'\ \ last modified '"$mtime"'</font></td>'"\\
164: "'<td align="center" width=20%><a href="'$intro'">Introduction</a></td>'"\\
165: "'<td align="center" width=20%>Table of Contents<br><font size=-1>(<a href="index.html" target=_top>frame</a>/<a href="slide_list_noframes.html" target="_top">no frame</a>)</font></td>'"\\
166: "'<td align="center" width=20%><a href="print_pages.shtml" target="_top">Printable<br><font size=-1>(single file)</font></a></td>'"\\
167: "'<td align="right"><font size=-1>\© Dartmouth College</font></td>'"\\
168: "'</tr></table>'"\\
169: "
170:
171: if [[ $print = 0 ]]; then
172: # process for screen formatting, unless -p was specified
173: # <BODY> => Insert background image and default font size. Insert vertical spacer at end of file
174: # Insert page number (file ID) and navigation links immediately after BODY
175: # Place <A NAME="..."> tags around all <H[1-5]> tags. Leading and trailing whitespace is stripped
176: # from the tag content, as it breaks the anchor action.
177: # DETAIL metatags are handled separately
178:
179: print -R "$doctype"
180: print '<html>'
181: print '<!-- This was created automatically by buildhtml - do not edit -->'
182: print '<head>'
183:
184: [[ $nometa = 0 && -n "$author" ]] && print '<meta name="AUTHOR" content="'"$author"'">'
185: [[ $nometa = 0 && -n "$keywords" ]] && print '<meta name="keywords" content="'"$keywords"'">'
186: [[ $nometa = 0 && -n "$description" ]] && print '<meta name="description" content="'"$description"'">'
187: # Let the TITLE come through as written in the source file, but delete the <HTML> and <HEAD>
188:
189: sed \
190: -e 's#<[hH][tT][mM][lL]>##' \
191: -e 's#<[hH][eE][aA][dD]>##' \
192: -e 's!<[cC][oO][dD][eE]>!'"$code_start"'!g' \
193: -e 's!</[cC][oO][dD][eE]>!'"$code_end"'!g' \
194: -e 's!<MCODE> *\([^<]*\) *</MCODE>!'"$mancode"'!g' \
195: -e 's!^<[bB][oO][dD][yY].*$!'"$bodystart""$topnavlinks"'!' \
196: -e 's!^</[bB][oO][dD][yY]>!'"$vspace""$botnavlinks"'<br><hr>'"$lastmod"'</font></body>!' \
197: -e 's!<[hH]\([1-5]\)\([^>]*\)>[ ]*\(.*[^ ]\)[ ]*</[hH]\1>!<a name="L\1-\3"><h\1\2>\3</h\1></a>!' \
198: -e 's!<[hH]1>!'"$h1_start"'!g' \
199: -e 's!</[hH]1>!'"$h1_end"'!g' \
200: -e 's!<[hH]2>!'"$h2_start"'!g' \
201: -e 's!</[hH]2>!'"$h2_end"'!g' \
202: -e 's!<[hH]3>!'"$h3_start"'!g' \
203: -e 's!</[hH]3>!'"$h3_end"'!g' \
204:
205: else
206: # For printable version, use a smaller font and skip spacing at end of pages.
207: # Skip background images, and all HTML/BODY tages - we'll supply separate ones
208: # at start and end.
209: # Skip the internal anchors on <H> tags.
210: # Replace DETAIL metatags with the detail_start and detail_end codes, if any.
211: # Insert "newpage" code at start of each file, unless the $src is the introduction - quick
212: # hack to get pagination right at start of printed notes
213: case $src in
214: ${intro%.*}.*) class= ;;
215: *) class='class="newpage"' ;;
216: esac
217:
218: sed \
219: -e 's!<[hH][tT][mM][lL]>.*$!!' \
220: -e 's!</[hH][tT][mM][lL]>.*$!!' \
221: -e 's!<[hH][eE][aA][dD]>.*$!!' \
222: -e 's!</[hH][eE][aA][dD]>.*$!!' \
223: -e 's!<[tT][iI][tT][lL][eE].*$!!' \
224: -e "s!<[cC][oO][dD][eE]>!$code_start!g" \
225: -e "s!</[cC][oO][dD][eE]>!$code_end!g" \
226: -e 's!<MCODE> *\([^<]*\) *</MCODE>!'"$mancode"'!g' \
227: -e 's!^<[bB][oO][dD][yY].*$!<p align=RIGHT '$class'><font size=-1>'$fid'</font></P>!' \
228: -e 's!</[bB][oO][dD][yY]>!!' \
229: -e 's!<[hH]1>!'"$h1_start"'!g' \
230: -e 's!</[hH]1>!'"$h1_end"'!g' \
231: -e 's!<[hH]2>!'"$h2_start"'!g' \
232: -e 's!</[hH]2>!'"$h2_end"'!g' \
233: -e 's!<[hH]3>!'"$h3_start"'!g' \
234: -e 's!</[hH]3>!'"$h3_end"'!g' \
235: -e 's#<!-- *DETAIL-.* -->#'"$detail_start"'#g' \
236: -e 's#<!-- */DETAIL.* -->#'"$detail_end"'#g' \
237:
238: fi
239: }
240:
241: create_detail()
242: {
243: # Create a "detail" file by processing the DETAIL metatags.
244: # We operate on the HTML file already created, so that we don't need to worry
245: # about all the other translations.
246: # The non-blank characters following DETAIL- in the tag are used to create anchors
247: # for the links between the two versions.
248:
249: # $1 is the name of the .[s]html file we need to process.
250: # We will replace $1 with a new version, and create a .detail.[s]html file to go with it.
251: src=$1
252:
253: # Look for variables optionally set in buildhtml.conf, or in pre-existing
254: # environment variables.
255: # detail_start, detail_end
256:
257: # Link for "detail" page. The "default" page hides the detail - turns them
258: # into an HTML comment, and inserts a link to the page with the details included.
259: # For the printable version, we ignore the detail metatags, which leaves the content in the document.
260: # A side effect of this is that detail_start and detail_end tags don't appear in the printable
261: # copy
262:
263: detail=${src%.$html}.detail.$html
264: moredetail="\\
265: "'<a name=\1></a>'"\\
266: "'<a href="'"$detail"'\#\1"><img src="right.gif" border=0 height=15 width=15 alt="More detail"></a>'"\\
267: "
268: lessdetail="\\
269: "'<a name=\2></a>'"\\
270: "'<a href="'"$src"'\#\2"><img src="down.gif" border=0 height=15 width=15 alt="Less detail"></a>'"\\
271: "
272: # First create the modified "standard" file, turning the DETAIL content into comments and inserting a link
273: sed < $src > $src.$$ \
274: -e 's#<!-- *DETAIL-\([^ ]*\).*-->#'"$moredetail"'<!-- #' \
275: -e 's#<!-- */DETAIL.*--># -->#'
276:
277: # Now create the "detail" file
278: sed < $src > $detail \
279: -e 's#\(<!-- *DETAIL-\([^ ]*\).*-->\)#'"$lessdetail"'\1'"$detail_start"'#' \
280: -e 's#\(<!-- */DETAIL.*-->\)#'"$detail_end"'\1#'
281:
282: ## -e 's#\(<!-- *DETAIL-\([^ ]*\).*-->\)#'"$lessdetail"'\1#'
283:
284: # overwrite the original input file.
285: mv $src.$$ $src
286: }
287:
288: print_start()
289: {
290: # print HTML header fluff - this will be stripped out of all component files.
291: # Arguments expected are:
292: # 1 = the title of the combined document.
293: # 2 = base URL (optional)
294: # Since we use server side include here, could suck in custom preface for a given class
295:
296: print "$doctype"
297: print "<html>"
298: print "<head>"
299: print "<!-- This file is generated automatically by buildhtml. Do not edit -->"
300: [[ $nometa = 0 && -n "$author" ]] && print '<meta name="AUTHOR" content="'"$author"'">'
301: [[ $nometa = 0 && -n "$keywords" ]] && print '<meta name="keywords" content="'"$keywords"'">'
302: [[ $nometa = 0 && -n "$description" ]] && print '<meta name="description" content="'"$description"'">'
303: print "<title>$1</title>"
304: # Some CSS magic to allow us to force pagebreaks in the printed output
305: print "<style type="text/css" media="print"> .newpage { page-break-before: always; }</style>"
306: print "<!--#config timefmt=\"%d %B %Y\" -->"
307: print "</head>"
308:
309: # Leave body at default browser font. Components will be resized relative to this.
310: print "<body bgcolor=WHITE>"
311: print "<h1 align=CENTER>$1</h1>"
312: print "<h3 align=CENTER> Course Handout: (last update <!--#echo var=\"LAST_MODIFIED\"-->) </h3>"
313: print "<br>"
314: [[ ! -z $2 ]] && print "<small>These notes may be found at <a href=\"$2\">$2</a>. The online version has many links to additional"\
315: "information and may be more up to date than the printed notes</small>"
316:
317: print "<hr>"
318: }
319:
320: print_end()
321: {
322: # print HTML trailer fluff - this has been stripped out of all the component files.
323: # Arguments expected are:
324: # 1 = the title of the combined document.
325: # 2 = base URL (optional)
326:
327: # Turn the title into something we can use for a download counterID
328: counterid=$(print $1 | sed -es'/ \{1,\}/_/g' -es'/[^A-Za-z0-9_]//g')
329:
330: print "<br><br><hr>"
331: print "<font size=-1>$1: Course Handout</font><br>"
332: # This assumes the simplecounter CGI is available on the system used to deliver the page.
333: # Version with visible counter:
334: # print '<font size=-1>Download count [ <!--#include virtual="/cgi-bin/cgiwrap/~richard/simplecounter.cgi?counterid='$counterid'" --> ] </font>'
335: # Version with invisible counter
336: print '<!--#include virtual="/cgi-bin/cgiwrap/~richard/simplecounter.cgi?counterid='$counterid'&silent=true" -->'
337:
338: print "<font size=-2>"
339: print "(last update <!--#echo var=\"LAST_MODIFIED\"-->) ©Dartmouth College"
340: [[ ! -z $2 ]] && print " $2"
341: print "</font>"
342:
343: print "</font>"
344: print "</body>"
345: print "</html>"
346: }
347:
348: # Control starts here
349:
350: integer i maxslide
351: print=0
352: stream=0
353: nometa=0
354: baseurl=
355: doctype=${doctype:-"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">"}
356: frametype=${frametype:-"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Frameset//EN\" \"http://www.w3.org/TR/REC-html40/frameset.dtd\">"}
357: html=html
358:
359: # Option -p = format for printer-optimized HTML (default is screen)
360: # Option -s = output to stdout, even if named files are given to us. Don't create the .html files.
361: # Option -b = specify a BASE URL
362: # Option -n = no META tags in header (author, keywords etc.)
363: # Option -S = create .shtml instead of .html as output (file contains SSI)
364: while getopts npsSb: o ; do
365: case $o in
366: n) nometa=1;;
367: p) print=1;;
368: s) stream=1;;
369: S) html=shtml;;
370: b) baseurl=$OPTARG;;
371: esac
372: done
373: shift $OPTIND-1
374:
375: # Get the intro page name from line 1 of slide.order
376: intro=$(sed -n -e '1s/^[ 0-9]*//p' slide.order)
377:
378: # Get the "class" title from the TITLE of the intro page
379: # We'll use this in the printable version
380: if [[ -r ${intro%.*}.src ]] ; then
381: ctitle=$(grep -i '<TITLE' ${intro%.*}.src | sed -e 's#.*<[tT][iI][tT][lL][eE]>##' -e 's#</[tT][iI][tT][lL][eE]>.*##')
382: else
383: # Oops - well just use a generic title
384: ctitle="Class Handout"
385: fi
386:
387: # Set up the bodytag variable which is used in a couple of functions
388: # <BODY BACKGROUND=background> image or colour. Default is white, special value "none"
389: # omits the tag and lets the browser default be used
390: body_bg="${body_bg:-WHITE}"
391: case $body_bg in
392: (*.gif|*.jpg)
393: # Assume it is an image
394: bodytag="background=$body_bg" ;;
395: none)
396: # We don't want any background
397: bodytag= ;;
398: *)
399: # Assume it is a colour
400: bodytag="bgcolor=$body_bg" ;;
401: esac
402:
403: if [[ $# -gt 0 ]] ; then
404: # There are arguments - loop over all the .src files and turn them into .html
405: # This code assumes the filenames have the form *.src
406: # The '*' part is extracted and used as the file ID tag.
407:
408: # Print the headers, for the printable version
409: [[ $print = 1 ]] && print_start "$ctitle" "$baseurl"
410:
411: # maxslide is the number of the last slide in the sequence
412: maxslide=$(tail -1 slide.order|awk '{print $1}')
413:
414: for srcfile in $@; do
415: # If we were give .[s]html names, but the .src files existed, assume we meant the .src
416: # The suffix we want is stored in $html for later use
417: case $srcfile in
418: *.html)
419: html=html
420: newfile=${srcfile%.$html}.src
421: [[ -r $newfile ]] && srcfile=$newfile
422: ;;
423: *.shtml)
424: html=shtml
425: newfile=${srcfile%.$html}.src
426: [[ -r $newfile ]] && srcfile=$newfile
427: ;;
428: esac
429:
430: # strip off the .{src|html} to get a base name - use that to look up the
431: # file numbering from slide.order, or just use it directly as a fileID
432: case $srcfile in
433: *.src)
434: base=${srcfile%.src} ;;
435: *)
436: base=$srcfile%.$html}
437: esac
438:
439: # The slide.order file is [spaces]NN[spaces]filename
440: # The counting starts at 0, with the introduction page.
441: # $fidno is the number of the file in the slide.order list.
442: # $fid is the "fileID" we will use to label the pages.
443: fidno=$(grep " $base\." slide.order | awk '{print $1}')
444: fid=$fidno
445: # If that didn't work, just use $base
446: [[ -z $fid ]] && fid=$base
447:
448: # We don't want "page numbers" for the intro and slide list - they look silly so special case it here
449: case $fid in
450: ${intro%.*}.*) fid= ;;
451: (*slide_list*|0) fid= ;;
452: esac
453:
454: # Finally, if fid is non-null, put it in ()
455: [[ ! -z $fid ]] && fid="($fid)"
456:
457: # Get the modified time for $srcfile, for use in the footers.
458: mtime=$(mtime $srcfile)
459:
460: # Make the outfile by replacing .src with .$html. If the given filename
461: # wasn't a .src file, this will just append .$html. We need this for the prev/next links
462: # even if we are writing to stdout.
463: outfile=${srcfile%.src}.$html
464:
465: # Get the "Previous" and "Next" files from the slide.order list for the navigation buttons
466: # $i is an integer, so null evaluates to 0
467: if [[ -z "$fidno" ]]; then
468: # We weren't in the slide.order file, so skip the navigation buttons
469: prev=
470: next=
471: else
472: # look up the previous and next files for the nav buttons
473: prev=$outfile
474: next=$outfile
475: i=$fidno
476: [[ $i -gt 0 ]] && prev=$(egrep '^ *'$((i-1))' ' slide.order|awk '{print $2}')
477: [[ $i -lt $maxslide ]] && next=$(egrep '^ *'$((i+1))' ' slide.order|awk '{print $2}')
478:
479: # for the multiframe model, the next/previous links are not the actual .html files, but the framesets
480: # prev=${prev%.*html}.frameset.html
481: # next=${next%.*html}.frameset.html
482: fi
483:
484: ptitle=$(grep -i '<TITLE' $srcfile | sed -e 's#.*<[tT][iI][tT][lL][eE]>##' -e 's#</[tT][iI][tT][lL][eE]>.*##')
485:
486: # Call the editing function on this file, with I/O redirected as needed
487: # $fid is not optional, but may have a null value, so quote it.
488: if [[ $stream = 1 ]]; then
489: edit_file < $srcfile $srcfile "$fid" $prev $next "$ptitle"
490: # If we are writing to stdout, we skip the "detail" file
491: else
492: edit_file < $srcfile > $outfile $srcfile "$fid" $prev $next "$ptitle"
493:
494: # If the srcfile contains DETAIL metatags, create the *.detail.html file
495: # Note that edit_file() leaves DETAIL tags alone, since we need to treat them
496: # differently for the "standard" file and the "detail" file.
497: if grep -q '<!-- *DETAIL.*-->' $srcfile ; then
498: create_detail $outfile
499: fi
500:
501: # Multiframe version needed functions to create *.frameset.html and *.nav.html files
502: # per named source file. See previous version for that code.
503: fi
504: done
505: [[ $print = 1 ]] && print_end "$ctitle" "$baseurl"
506: else
507: # There are no arguments - do the same thing to stdin, write to stdout. No FileID
508: mtime=$(date '+%d/%m/%Y')
509: [[ $print = 1 ]] && print_start "Class Notes" "$baseurl"
510: edit_file
511: [[ $print = 1 ]] && print_end "Class Notes" "$baseurl"
512: fi
513: exit 0
| last modified 02/04/2009 | Introduction | Table of Contents (frame/no frame) |
Printable (single file) |
© Dartmouth College |