Skip to content
Snippets Groups Projects
Commit 27f14dcc authored by Tim Hunt's avatar Tim Hunt
Browse files

New maths rendering framework and docs system.

Currently, the maths rendering only implements MathJax.

The docs system changes mainly moves more code into functions where it
can be unit tested.
parent 396857e9
No related branches found
No related tags found
No related merge requests found
......@@ -17,17 +17,14 @@
/**
* Documentation system as a static wiki of markdown.
*
* @package stackDoc
* @package qtype_stack
* @author Ben Holmes
* @copyright 2012 The University of Birmingham
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(dirname(__FILE__) . '/../../../../config.php');
require_once($CFG->libdir . '/markdown.php');
require_once(dirname(__FILE__) . '/../locallib.php');
require_once(dirname(__FILE__) . '/../stack/utils.class.php');
require_once(dirname(__FILE__) . '/docslib.php');
/*
* This file serves the contents of a local directory and renders markup to html
......@@ -42,20 +39,12 @@ if (substr($_SERVER['REQUEST_URI'], -7) == 'doc.php') {
exit();
}
//require_login();
$moodleroot = $CFG->dirroot;
$webroot = $CFG->wwwroot;
$docs = '/question/type/stack/doc/' . $CFG->lang; // Docs local location.
$webdocs = $webroot.'/question/type/stack/doc/doc.php'; // Doc.php web address.
$webpix = $CFG->wwwroot.'/question/type/stack/pix/logo-sm.png';
$doclog = stack_utils::convert_slash_paths($CFG->dataroot . '/stack/logs');
$docsroot = $CFG->dirroot . '/question/type/stack/doc/' . current_language();
$docsurl = $CFG->wwwroot . '/question/type/stack/doc/doc.php';
// The URL to the directory for static content to be served by the docs
// access this string in the docs with %CONTENT.
$docscontent = $webroot.'/question/type/stack/doc/content';
$docscontent = $CFG->wwwroot . '/question/type/stack/doc/content';
$context = context_system::instance();
$PAGE->set_context($context);
......@@ -64,123 +53,57 @@ $PAGE->set_title(stack_string('stackDoc_docs'));
if (substr($_SERVER['REQUEST_URI'], -7) == 'doc.php') {
// Don't access doc.php directly, treat it like a directory instead.
$uri = '/'; // The uri string.
$uri = '/';
} else {
$uri = get_file_argument(); // The uri string.
$uri = get_file_argument();
}
$segs = explode('/', $uri);
$lastseg = $segs[count($segs) - 1];
$body = '';
$header = '';
// Links for the end of the page.
if ($uri == '/') {
// I.e. at doc.php/ the docs front page.
$links = array($webdocs.'/Site_map' => stack_string('stackDoc_siteMap'));
// The docs front page at .../doc.php/.
$linkurls = array($docsurl . '/Site_map' => stack_string('stackDoc_siteMap'));
} else if ('/Site_map' == $uri) {
$links = array($webdocs => stack_string('stackDoc_home'));
$linkurls = array($docsurl => stack_string('stackDoc_home'));
} else {
$links = array($webdocs => stack_string('stackDoc_home'),
$linkurls = array($docsurl => stack_string('stackDoc_home'),
'./' => stack_string('stackDoc_index'),
'../' => stack_string('stackDoc_parent'),
$webdocs.'/Site_map' => stack_string('stackDoc_siteMap'));
$docsurl . '/Site_map' => stack_string('stackDoc_siteMap'));
}
$linkstrs = array();
foreach ($links as $url => $link) {
$linkstrs[] = "<a href=\"$url\">$link</a>";
$links = array();
foreach ($linkurls as $url => $link) {
$links[] = '<a href="' . $url . '">' . $link . '</a>';
}
$linkstr = implode(' | ', $linkstrs);
$links = implode(' | ', $links);
if ('Site_map' == $lastseg) {
$body .= $linkstr;
$body .= '<h3>' . stack_string('stackDoc_directoryStructure', 'stack') . '</h3>';
$body .= index($moodleroot . $docs, $webdocs); // Assumes at a file in /.
$body = stack_docs_site_map($links, $docsroot, $docsurl);
} else {
if ('' == $lastseg) {
$doc = 'index.md';
$file = $docsroot . $uri . 'index.md';
} else {
$doc = '';
}
$file = $moodleroot . $docs . $uri . $doc;
if (!file_exists($file)) {
$header= 'HTTP/1.0 404 Not Found';
$file = $docsroot . $uri;
}
if (file_exists($file)) {
$body = stack_docs_page($links, $file, $docscontent);
$handle = fopen($file, 'r');
$page = fread($handle, filesize($file));
fclose($handle);
$page = preg_replace('/\\\%CONTENT/', '$$$PARSE_ERROR', $page); // Escaped \%CONTENT won't get processed.
$page = preg_replace('/\%CONTENT/', $docscontent, $page);
$page = preg_replace('/\$\$\$PARSE_ERROR/', '%CONTENT', $page);
$body .= $linkstr;
$body .= "\n<hr/>\n";
if (pathinfo($file, PATHINFO_EXTENSION) == 'md') {
$page = str_replace("\\", "\\\\", $page);
$options = new stdClass();
$options->noclean = true;
$body .= format_text(Markdown($page), FORMAT_HTML, $options); // Render it, in this case in Markdown.
} else {
$body .= $page;
$body = stack_docs_no_found($links);
}
$body .= "\n<hr/>\n";
$body .= $linkstr;
} else {
$body .= html_writer::tag('h1', stack_string('stackDoc_404'));
$body .= html_writer::tag('p', stack_string('stackDoc_404message'));
$body .= $linkstr;
}
}
$webpix = $CFG->wwwroot . '/question/type/stack/pix/logo-sm.png';
$pagetitle = '<img src="' . $CFG->wwwroot . '/question/type/stack/pix/logo-sm.png" style="margin-right: 10px;" />' .
stack_string('stackDoc_docs');
echo $OUTPUT->header($header);
$pagetitle = "<img src=\"{$webpix}\" style=\"margin-right: 10px;\" />".stack_string('stackDoc_docs');
echo $OUTPUT->header();
echo $OUTPUT->heading($pagetitle);
echo $body;
echo $OUTPUT->footer();
function index($d, $relpath = '') {
// Write a list describing the directory structure, recursive, discriminates for .md files.
$i = '<ul class="dir">';
if (is_dir($d)) {
if ($dh = opendir($d)) {
while (($f = readdir($dh)) !== false) {
if (substr($f, 0, 1) != '.') {
$fpath = "$d/$f";
if (filetype($fpath) == 'dir') {
$i .= "<li><a href=\"$relpath/$f/\">" . str_replace('_', ' ', $f)
. "</a>" . index($fpath, "$relpath/$f") . '</li>';
} else {
if ($f != 'index.md' && '.md' == substr($f, -3) && 'Site_map.md' != $f) {
$fname = pathinfo($fpath, PATHINFO_FILENAME);
$i .= "<li><a href=\"$relpath/$fname.md\">" . str_replace('_', ' ', $fname) . "</a></li>";
}
}
}
}
closedir($dh);
}
}
$i .= '</ul>';
return $i;
}
<?php
// This file is part of Stack - http://stack.bham.ac.uk/
//
// Stack is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Stack is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Stack. If not, see <http://www.gnu.org/licenses/>.
/**
* Documentation system as a static wiki of markdown.
*
* @package qtype_stack
* @author Ben Holmes
* @copyright 2012 The University of Birmingham
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->libdir . '/markdown.php');
require_once(dirname(__FILE__) . '/../locallib.php');
require_once(dirname(__FILE__) . '/../stack/utils.class.php');
/**
* Display the STACK documentation index for a particular path.
* @param string $dir directory.
* @param string $relpath relative path to that directory.
*/
function stack_docs_index($dir, $relpath = '') {
// Write a list describing the directory structure, recursive, discriminates for .md files.
$exclude = array('index.md', 'Site_map.md');
if (!is_dir($dir)) {
return '';
}
$items = array();
foreach (glob($dir . '/*') as $filepath) {
$filename = basename($filepath);
if (substr($filename, 0, 1) === '.' || in_array($filename, $exclude)) {
continue;
}
if (is_dir($filepath)) {
$items[] = "<li><a href=\"$relpath/$filename/\">" . stack_docs_title_from_filename($filename) . "</a>" .
stack_docs_index($filepath, "$relpath/$filename") . '</li>';
} else {
$items[] = "<li><a href=\"$relpath/$filename\">" . stack_docs_title_from_filename($filename) .
"</a></li>";
}
}
if (empty($items)) {
return '';
}
return '<ul class="dir">' . implode('', $items) . '</ul>';
}
/**
* Convert a file-name to a nice title.
* @param string $filename a filename.
* @return string the corresponding title.
*/
function stack_docs_title_from_filename($filename) {
return str_replace(array('_', '.md'), array(' ', ''), $filename);
}
/**
* Generate the documentation site-map.
* @param string $links menu of links to show.
* @param string $docsroot file path of the root of the docs.
* @param string $docsurl base URL for the docs.
* @return string HTML page body.
*/
function stack_docs_site_map($links, $docsroot, $docsurl) {
$body = '';
$body .= $links;
$body .= '<h3>' . stack_string('stackDoc_directoryStructure', 'stack') . '</h3>';
$body .= stack_docs_index($docsroot, $docsurl);
return $body;
}
/**
* Generate an error page when missing docs are referred to.
* @param string $links menu of links to show.
* @return string HTML page body.
*/
function stack_docs_no_found($links) {
$body = '';
$body .= html_writer::tag('h1', stack_string('stackDoc_404'));
$body .= html_writer::tag('p', stack_string('stackDoc_404message'));
$body .= $links;
return $body;
}
/**
* Generate a page of the documentation from the source in a file.
* @param string $links menu of links to show.
* @param string $file path to the file to display.
* @param string $docscontent base URL for linking to images etc.
* @return string HTML page body.
*/
function stack_docs_page($links, $file, $docscontent) {
$body = '';
$body .= $links;
$body .= "\n<hr/>\n";
$body .= stack_docs_render_markdown(file_get_contents($file), $docscontent);
$body .= "\n<hr/>\n";
$body .= $links;
return $body;
}
/**
* @param string $page countent in Markdown format.
* @param string $docscontent base URL for linking to images etc.
* @return string HTML content.
*/
function stack_docs_render_markdown($page, $docscontent) {
// Put in links to images etc.
$page = preg_replace('~(?<!\\\\)%CONTENT~', $docscontent, $page);
$page = str_replace('\%CONTENT', '%CONTENT', $page);
$page = stack_maths::pre_process_docs_page($page);
$page = Markdown($page);
$page = stack_maths::post_process_docs_page($page);
return $page;
}
<?php
// This file is part of Stack - http://stack.bham.ac.uk/
//
// Stack is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Stack is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Stack. If not, see <http://www.gnu.org/licenses/>.
/**
* Unit tests for the documentation library functions.
*
* @package qtype_stack
* @copyright 2012 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(dirname(__FILE__) . '/../docslib.php');
/**
* Unit tests for the documentation library functions.
*
* @copyright 2012 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @group qtype_stack
*/
class stack_docslib_test extends basic_testcase {
public function test_stack_docs_title_from_filename() {
$this->assertEquals('About', stack_docs_title_from_filename('About'));
$this->assertEquals('Some folder', stack_docs_title_from_filename('Some_folder'));
$this->assertEquals('Documentation', stack_docs_title_from_filename('Documentation.md'));
$this->assertEquals('Future plans', stack_docs_title_from_filename('Future_plans.md'));
}
public function test_stack_docs_index() {
global $CFG;
$this->assertEquals('', stack_docs_index($CFG->dirroot . '/question/type/stack/doc/en/Installation',
$CFG->wwwroot . '/question/type/stack/doc.php/Installation'));
$this->assertEquals(str_replace('WWWROOT', $CFG->wwwroot, '<ul class="dir">' .
'<li><a href="WWWROOT/question/type/stack/doc.php/Students/Accessibility.md">Accessibility</a></li>' .
'<li><a href="WWWROOT/question/type/stack/doc.php/Students/Answer_assessment.md">Answer assessment</a></li>' .
'<li><a href="WWWROOT/question/type/stack/doc.php/Students/Answer_input.md">Answer input</a></li>' .
'<li><a href="WWWROOT/question/type/stack/doc.php/Students/FAQ.md">FAQ</a></li></ul>'),
stack_docs_index($CFG->dirroot . '/question/type/stack/doc/en/Students',
$CFG->wwwroot . '/question/type/stack/doc.php/Students'));
}
public function test_stack_docs_render_markdown() {
$this->assertEquals("<p>Test</p>\n",
stack_docs_render_markdown('Test', '.../doc/content'));
$this->assertEquals('<p><a href=".../doc/content/readme.txt">Readme</a></p>' . "\n",
stack_docs_render_markdown('[Readme](%CONTENT/readme.txt)', '.../doc/content'));
$this->assertEquals("<p>Literal %CONTENT</p>\n",
stack_docs_render_markdown('Literal \%CONTENT', '.../doc/content'));
$this->assertEquals("<p><code>\(x^2\)</code> gives &#92;(x^2&#92;).</p>\n",
stack_docs_render_markdown('`\(x^2\)` gives \(x^2\).', '.../doc/content'));
}
}
......@@ -15,6 +15,9 @@
// along with Stack. If not, see <http://www.gnu.org/licenses/>.
require_once(dirname(__FILE__) . '/stack/mathsoutput/mathsoutput.class.php');
/**
* Base class for all the types of exception we throw.
*/
......@@ -25,7 +28,7 @@ class stack_exception extends moodle_exception {
}
function stack_string($key, $a = null) {
return get_string($key, 'qtype_stack', $a);
return stack_maths::process_lang_string(get_string($key, 'qtype_stack', $a));
}
/**
......@@ -61,7 +64,6 @@ function stack_maxima_translate($rawfeedback) {
$rawfeedback = str_replace(']]', '', $rawfeedback);
$rawfeedback = str_replace('\n', '', $rawfeedback);
$rawfeedback = str_replace('\\', '\\\\', $rawfeedback);
$rawfeedback = str_replace('$', '\$', $rawfeedback);
$rawfeedback = str_replace('!quot!', '"', $rawfeedback);
ob_start();
......
<?php
// This file is part of Stack - http://stack.bham.ac.uk/
//
// Stack is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Stack is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Stack. If not, see <http://www.gnu.org/licenses/>.
/**
* Public API for other parts of STACK to call in order to process equations.
*/
require_once(dirname(__FILE__) . '/mathsoutputbase.class.php');
/**
* Public API to the maths rendering system.
*
* @copyright 2012 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class stack_maths {
/** @var array output name => instance. */
protected static $outputs = array();
/**
* Do the necessary processing on equations in a language string, before it is output.
* @param string $string the language string, as loaded by get_string.
* @return string the string, with equations rendered to HTML.
*/
public static function process_lang_string($string) {
return self::get_output()->process_lang_string($string);
}
/**
* Do the necessary processing on documentation page before the content is
* passed to Markdown.
* @param string $docs content of the documentation file.
* @return string the documentation content ready to pass to Markdown.
*/
public static function pre_process_docs_page($docs) {
return self::get_output()->pre_process_docs_page($docs);
}
/**
* Do the necessary processing on documentation page after the content is
* has been rendered by Markdown.
* @param string $html rendered version of the documentation page.
* @return string rendered version of the documentation page with equations inserted.
*/
public static function post_process_docs_page($html) {
return self::get_output()->post_process_docs_page($html);
}
/**
* Do the necessary processing on content that came from the user, for example
* the question text or general feedback. The result of calling this method is
* then passed to Moodle's {@link format_text()} function.
* @param string $text the content to process.
* @return string the content ready to pass to format_text.
*/
public static function pre_process_user_input($text) {
return self::get_output()->pre_process_user_input($text,
stack_utils::get_config()->replacedollars);
}
/**
* @return stack_maths_output the output method that has been set in the
* configuration options.
*/
protected static function get_output() {
$class = self::class_for_type(stack_utils::get_config()->mathsdisplay);
return new $class();
}
/**
* The class name corresponding to an output method.
* @param string $type the output method name.
* @return string the corresponding class name.
*/
protected static function class_for_type($type) {
global $CFG;
$file = dirname(__FILE__) . "/mathsoutput{$type}.class.php";
$class = "stack_maths_output_{$type}";
if (!is_readable($file)) {
throw new stack_exception('stack_maths: unknown output method ' . $type);
}
include_once($file);
if (!class_exists($class)) {
throw new stack_exception('stack_maths: output method ' . $type .
' does not define the expected class ' . $class);
}
return $class;
}
}
<?php
// This file is part of Stack - http://stack.bham.ac.uk/
//
// Stack is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Stack is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Stack. If not, see <http://www.gnu.org/licenses/>.
/**
* The base class for STACK maths output methods.
*
* @copyright 2012 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class stack_maths_output {
/**
* Do the necessary processing on equations in a language string, before it is output.
* @param string $string the language string, as loaded by get_string.
* @return string the string, with equations rendered to HTML.
*/
public function process_lang_string($string) {
return $string;
}
/**
* Do the necessary processing on documentation page before the content is
* passed to Markdown.
* @param string $docs content of the documentation file.
* @return string the documentation content ready to pass to Markdown.
*/
public function pre_process_docs_page($docs) {
// Double all the \ characters, since Markdown uses it as an escape char,
// but we use it for maths.
return str_replace('\\', '\\\\', $docs);
}
/**
* Do the necessary processing on documentation page after the content is
* has been rendered by Markdown.
* @param string $html rendered version of the documentation page.
* @return string rendered version of the documentation page with equations inserted.
*/
public function post_process_docs_page($html) {
// Now, undo the doubling of the \\ characters inside <code> regions.
return preg_replace_callback('~<code>(.*?)</code>~s',
function ($match) {
return '<code>' . str_replace('\\\\', '\\', $match[1]) . '</code>';
} , $html);
return $html;
}
/**
* Do the necessary processing on content that came from the user, for example
* the question text or general feedback. The result of calling this method is
* then passed to Moodle's {@link format_text()} function.
* @param string $text the content to process.
* @return string the content ready to pass to format_text.
*/
public function pre_process_user_input($text, $replacedollars) {
return $text; // TODO replace dollars option.
}
}
<?php
// This file is part of Stack - http://stack.bham.ac.uk/
//
// Stack is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Stack is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Stack. If not, see <http://www.gnu.org/licenses/>.
/**
* STACK maths output methods for using MathJax.
*
* @copyright 2012 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class stack_maths_output_mathjax extends stack_maths_output {
// Default implementation in the base class does what we want!
}
<?php
// This file is part of Stack - http://stack.bham.ac.uk/
//
// Stack is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Stack is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Stack. If not, see <http://www.gnu.org/licenses/>.
/**
* Unit tests for the MathJax maths output class.
*
* @package qtype_stack
* @copyright 2012 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once(dirname(__FILE__) . '/../mathsoutput.class.php');
require_once(dirname(__FILE__) . '/../../../doc/docslib.php');
/**
* Unit tests for the MathJax maths output class.
*
* @copyright 2012 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @group qtype_stack
*/
class stack_maths_mathjax_test extends basic_testcase {
public function test_maths_output_mathsjax() {
// MathJax output is the default.
$this->assertEquals('Your answer needs to be a single fraction of the form \( {a}\over{b} \). ', stack_string('ATSingleFrac_part'));
$this->assertEquals("<p><code>\(x^2\)</code> gives &#92;(x^2&#92;).</p>\n",
stack_docs_render_markdown('`\(x^2\)` gives \(x^2\).', ''));
$this->assertEquals('What is \(x^2\)?', stack_maths::pre_process_user_input('What is \(x^2\)?'));
// TODO replacedollars option.
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment