Difference between revisions of "Mediawiki RawFile"

From YobiWiki
Jump to navigation Jump to search
m (Renaming snippet -> file)
Line 1: Line 1:
 
==Very short introduction==
 
==Very short introduction==
Just have a look to the 2 [[Mediawiki_RawSnippet#Short_example|examples]] to see how to use the extension
+
Just have a look to the 2 [[Mediawiki_RawFile#Short_example|examples]] to see how to use the extension
<br>and to the [[Mediawiki_RawSnippet#Installation|Installation]] section to see how to install the extension in your [[MediaWiki]] server
+
<br>and to the [[Mediawiki_RawFile#Installation|Installation]] section to see how to install the extension in your [[MediaWiki]] server
 
==Introduction==
 
==Introduction==
 
Originally the idea was to be able to download directly a portion of code as a file.
 
Originally the idea was to be able to download directly a portion of code as a file.
Line 23: Line 23:
 
There are 2 kinds of elements to add to the wiki language:
 
There are 2 kinds of elements to add to the wiki language:
 
* anchors that will flag which code blocks belong to a specific file
 
* anchors that will flag which code blocks belong to a specific file
** <code><nowiki>{{#rawsnippetAnchor: myscript.sh}}</nowiki></code>
+
** <code><nowiki>{{#fileAnchor: myscript.sh}}</nowiki></code>
 
** Not visible in the regular wiki display
 
** Not visible in the regular wiki display
 
* links that will allow to download the file
 
* links that will allow to download the file
** <code><nowiki>{{#rawsnippetLink: myscript.sh}}</nowiki></code>
+
** <code><nowiki>{{#fileLink: myscript.sh}}</nowiki></code>
** Transformed into new regular wikicode that will be eventually transformed to real URLs: <br><code><nowiki>{{fullurl:{{PAGENAME}}|action=raw&rawsnippet=myscript.sh}}</nowiki></code><br><code><nowiki>http://wiki.yobi.be/index.php?title=Mediawiki_RawSnippet&action=raw&rawsnippet=myscript.sh</nowiki></code>
+
** Transformed into new regular wikicode that will be eventually transformed to real URLs: <br><code><nowiki>{{fullurl:{{PAGENAME}}|action=raw&file=myscript.sh}}</nowiki></code><br><code><nowiki>http://wiki.yobi.be/index.php?title=Mediawiki_RawFile&action=raw&file=myscript.sh</nowiki></code>
   
 
For regular use, when a single code block is used and when the download link can be at the same position as the anchor, there is a shortcut notation mixing both anchor & link properties:
 
For regular use, when a single code block is used and when the download link can be at the same position as the anchor, there is a shortcut notation mixing both anchor & link properties:
<code><nowiki>{{#rawsnippet: myscript.sh}}</nowiki></code>
+
<code><nowiki>{{#file: myscript.sh}}</nowiki></code>
   
 
===Short example===
 
===Short example===
<pre>Let's save the following code [{{#rawsnippet: myscript.sh}} as myscript.sh]
+
<pre>Let's save the following code [{{#file: myscript.sh}} as myscript.sh]
 
<source lang=bash>
 
<source lang=bash>
 
#!/bin/bash
 
#!/bin/bash
Line 43: Line 43:
 
will give:
 
will give:
 
----
 
----
Let's save the following code [{{#rawsnippet: myscript.sh}} as myscript.sh]
+
Let's save the following code [{{#file: myscript.sh}} as myscript.sh]
 
<source lang=bash>
 
<source lang=bash>
 
#!/bin/bash
 
#!/bin/bash
Line 56: Line 56:
 
<pre>
 
<pre>
 
Let's start with the Bash usual header:
 
Let's start with the Bash usual header:
{{#rawsnippetanchor: myotherscript.sh}}
+
{{#fileanchor: myotherscript.sh}}
 
<source lang=bash>
 
<source lang=bash>
 
#!/bin/bash
 
#!/bin/bash
 
</source>
 
</source>
 
Then we'll display a welcome message:
 
Then we'll display a welcome message:
{{#rawsnippetanchor: myotherscript.sh}}
+
{{#fileanchor: myotherscript.sh}}
 
<source lang=bash>
 
<source lang=bash>
 
echo 'Welcome on earth!'
 
echo 'Welcome on earth!'
 
exit 0
 
exit 0
 
</source>
 
</source>
[{{#rawsnippetlink: myotherscript.sh}} myotherscript.sh is now available for download below the code]
+
[{{#filelink: myotherscript.sh}} myotherscript.sh is now available for download below the code]
 
</pre>
 
</pre>
 
will give:
 
will give:
 
----
 
----
 
Let's start with the Bash usual header:
 
Let's start with the Bash usual header:
{{#rawsnippetanchor: myotherscript.sh}}
+
{{#fileanchor: myotherscript.sh}}
 
<source lang=bash>
 
<source lang=bash>
 
#!/bin/bash
 
#!/bin/bash
 
</source>
 
</source>
 
Then we'll display a welcome message:
 
Then we'll display a welcome message:
{{#rawsnippetanchor: myotherscript.sh}}
+
{{#fileanchor: myotherscript.sh}}
 
<source lang=bash>
 
<source lang=bash>
 
echo 'Welcome on earth!'
 
echo 'Welcome on earth!'
 
exit 0
 
exit 0
 
</source>
 
</source>
[{{#rawsnippetlink: myotherscript.sh}} myotherscript.sh is now available for download below the code]
+
[{{#filelink: myotherscript.sh}} myotherscript.sh is now available for download below the code]
   
 
==The code==
 
==The code==
Which you can of course download just by following [{{#rawsnippetlink: RawSnippet.php}} this link :-)]
+
Which you can of course download just by following [{{#filelink: RawFile.php}} this link :-)]
   
 
So let's explain a bit the code in a Literate Programming way...
 
So let's explain a bit the code in a Literate Programming way...
 
===Hooks===
 
===Hooks===
 
First some hooks for our functions...
 
First some hooks for our functions...
{{#rawsnippetanchor: RawSnippet.php}}
+
{{#fileanchor: RawFile.php}}
 
<source lang=php>
 
<source lang=php>
 
<?php
 
<?php
Line 96: Line 96:
   
 
# Define a setup function
 
# Define a setup function
$wgExtensionFunctions[] = 'efRawSnippet_Setup';
+
$wgExtensionFunctions[] = 'efRawFile_Setup';
 
# Add a hook to initialise the magic words
 
# Add a hook to initialise the magic words
$wgHooks['LanguageGetMagic'][] = 'efRawSnippet_Magic';
+
$wgHooks['LanguageGetMagic'][] = 'efRawFile_Magic';
 
# Add a hook to intercept the raw output
 
# Add a hook to intercept the raw output
$wgHooks['RawPageViewBeforeOutput'][] = 'fnRawSnippet_Strip';
+
$wgHooks['RawPageViewBeforeOutput'][] = 'fnRawFile_Strip';
   
 
</source>
 
</source>
 
===Setup function===
 
===Setup function===
For the wiki parsing to create download links, rawsnippet and rawsnippetLink are equally treated, while rawsnippetAnchor will be simply left out.
+
For the wiki parsing to create download links, file and fileLink are equally treated, while fileAnchor will be simply left out.
{{#rawsnippetanchor: RawSnippet.php}}
+
{{#fileanchor: RawFile.php}}
 
<source lang=php>
 
<source lang=php>
function efRawSnippet_Setup() {
+
function efRawFile_Setup() {
 
global $wgParser;
 
global $wgParser;
# Set a function hook associating the "rawsnippet" magic word with our function
+
# Set a function hook associating the "file" magic word with our function
$wgParser->setFunctionHook( 'rawsnippet', 'efRawSnippet_Render' );
+
$wgParser->setFunctionHook( 'file', 'efRawFile_Render' );
$wgParser->setFunctionHook( 'rawsnippetlink', 'efRawSnippet_Render' );
+
$wgParser->setFunctionHook( 'filelink', 'efRawFile_Render' );
$wgParser->setFunctionHook( 'rawsnippetanchor', 'efRawSnippet_Empty' );
+
$wgParser->setFunctionHook( 'fileanchor', 'efRawFile_Empty' );
 
}
 
}
 
</source>
 
</source>
   
 
===Hook to initialize the magic words===
 
===Hook to initialize the magic words===
{{#rawsnippetanchor: RawSnippet.php}}
+
{{#fileanchor: RawFile.php}}
 
<source lang=php>
 
<source lang=php>
function efRawSnippet_Magic( &$magicWords, $langCode ) {
+
function efRawFile_Magic( &$magicWords, $langCode ) {
 
# Add the magic word
 
# Add the magic word
 
# The first array element is case sensitive, in this case it is not case sensitive
 
# The first array element is case sensitive, in this case it is not case sensitive
 
# All remaining elements are synonyms for our parser function
 
# All remaining elements are synonyms for our parser function
$magicWords['rawsnippet'] = array( 0, 'rawsnippet' );
+
$magicWords['file'] = array( 0, 'file' );
$magicWords['rawsnippetlink'] = array( 0, 'rawsnippetlink' );
+
$magicWords['filelink'] = array( 0, 'filelink' );
$magicWords['rawsnippetanchor'] = array( 0, 'rawsnippetanchor' );
+
$magicWords['fileanchor'] = array( 0, 'fileanchor' );
 
# unless we return true, other parser functions extensions will not get loaded.
 
# unless we return true, other parser functions extensions will not get loaded.
 
return true;
 
return true;
Line 135: Line 135:
 
<br>'''TODO''': what error to send out if there is no filename given?
 
<br>'''TODO''': what error to send out if there is no filename given?
 
<br>'''TODO''': supports links to files located in other local wiki pages, sth like 2nd arg default to $pagename='{{PAGENAME}}'
 
<br>'''TODO''': supports links to files located in other local wiki pages, sth like 2nd arg default to $pagename='{{PAGENAME}}'
{{#rawsnippetanchor: RawSnippet.php}}
+
{{#fileanchor: RawFile.php}}
 
<source lang=php>
 
<source lang=php>
function efRawSnippet_Render( &$parser, $filename = '') {
+
function efRawFile_Render( &$parser, $filename = '') {
 
# The parser function itself
 
# The parser function itself
 
# The input parameters are wikitext with templates expanded
 
# The input parameters are wikitext with templates expanded
 
# The output should be wikitext too
 
# The output should be wikitext too
return '{{fullurl:{{PAGENAME}}|action=raw&rawsnippet='.$filename.'}}';
+
return '{{fullurl:{{PAGENAME}}|action=raw&file='.$filename.'}}';
 
}
 
}
 
</source>
 
</source>
Line 147: Line 147:
 
<br>Curiously enough if the function doesn't exist at all the effect is exactly the same, MW doesn't throw any error.
 
<br>Curiously enough if the function doesn't exist at all the effect is exactly the same, MW doesn't throw any error.
 
<br>But let's keep things clean...
 
<br>But let's keep things clean...
{{#rawsnippetanchor: RawSnippet.php}}
+
{{#fileanchor: RawFile.php}}
 
<source lang=php>
 
<source lang=php>
function efRawSnippet_Empty( &$parser, $filename = '') {
+
function efRawFile_Empty( &$parser, $filename = '') {
 
return '';
 
return '';
 
}
 
}
Line 158: Line 158:
   
 
First let's see if <code>?action=raw</code> was used in the context of this extension: in that case we receive the filename as GET parameter, otherwise we simply return from our extension with return value=true which means we authorize the raw display (originally the hook was created to add an authentication point)
 
First let's see if <code>?action=raw</code> was used in the context of this extension: in that case we receive the filename as GET parameter, otherwise we simply return from our extension with return value=true which means we authorize the raw display (originally the hook was created to add an authentication point)
{{#rawsnippetanchor: RawSnippet.php}}
+
{{#fileanchor: RawFile.php}}
 
<source lang=php>
 
<source lang=php>
function fnRawSnippet_Strip(&$rawPage, &$text) {
+
function fnRawFile_Strip(&$rawPage, &$text) {
if (!isset($_GET['rawsnippet']))
+
if (!isset($_GET['file']))
 
return true;
 
return true;
$filename=$_GET['rawsnippet'];
+
$filename=$_GET['file'];
 
</source>
 
</source>
 
Raw action already set the headers with some client cache pragmas and is supposed to be displayed in the browser but in our case we want to make this "page" a downloadable file so we overwrite the headers which were defined and we add a few more, to ensure there is no caching on the client (it's very hard for the client to force a refresh on a file download, contrary to a web page) and to provide the adequate filename.
 
Raw action already set the headers with some client cache pragmas and is supposed to be displayed in the browser but in our case we want to make this "page" a downloadable file so we overwrite the headers which were defined and we add a few more, to ensure there is no caching on the client (it's very hard for the client to force a refresh on a file download, contrary to a web page) and to provide the adequate filename.
 
<br>At the end once we know the size of the data we'll transfer, we'll add a Content-Length header
 
<br>At the end once we know the size of the data we'll transfer, we'll add a Content-Length header
{{#rawsnippetanchor: RawSnippet.php}}
+
{{#fileanchor: RawFile.php}}
 
<source lang=php>
 
<source lang=php>
 
header("Content-disposition:filename=".$filename);
 
header("Content-disposition:filename=".$filename);
Line 179: Line 179:
 
<br>So we'll mask the literal blocks before searching for the anchors (we mask with the same string length because we'll retrieve an offset that we will use on the initial string and offsets must match)
 
<br>So we'll mask the literal blocks before searching for the anchors (we mask with the same string length because we'll retrieve an offset that we will use on the initial string and offsets must match)
 
<br>'''TODO''': should we care also of source, js, css, pre,... blocks?
 
<br>'''TODO''': should we care also of source, js, css, pre,... blocks?
{{#rawsnippetanchor: RawSnippet.php}}
+
{{#fileanchor: RawFile.php}}
 
<source lang=php>
 
<source lang=php>
 
$maskedtext=preg_replace_callback('/<nowiki>(.*?)<\/nowiki>/',
 
$maskedtext=preg_replace_callback('/<nowiki>(.*?)<\/nowiki>/',
Line 191: Line 191:
 
<br>And we free the memory used for the masked version
 
<br>And we free the memory used for the masked version
 
<br>'''TODO''': instead of cowardly returning if we don't find our anchors, we should cancel the headers and return a proper error page
 
<br>'''TODO''': instead of cowardly returning if we don't find our anchors, we should cancel the headers and return a proper error page
{{#rawsnippetanchor: RawSnippet.php}}
+
{{#fileanchor: RawFile.php}}
 
<source lang=php>
 
<source lang=php>
if (preg_match_all('/{{#rawsnippetanchor: +'.$filename.' *}}/i', $maskedtext, $matches, PREG_OFFSET_CAPTURE))
+
if (preg_match_all('/{{#fileanchor: +'.$filename.' *}}/i', $maskedtext, $matches, PREG_OFFSET_CAPTURE))
 
$offsets=$matches[0];
 
$offsets=$matches[0];
else if (preg_match_all('/{{#rawsnippet: +'.$filename.' *}}/i', $maskedtext, $matches, PREG_OFFSET_CAPTURE))
+
else if (preg_match_all('/{{#file: +'.$filename.' *}}/i', $maskedtext, $matches, PREG_OFFSET_CAPTURE))
 
$offsets=array($matches[0][0]);
 
$offsets=array($matches[0][0]);
 
else
 
else
Line 203: Line 203:
 
</source>
 
</source>
 
$text is both input & output so we copy it and start with an empty output.
 
$text is both input & output so we copy it and start with an empty output.
{{#rawsnippetanchor: RawSnippet.php}}
+
{{#fileanchor: RawFile.php}}
 
<source lang=php>
 
<source lang=php>
 
$textorig=$text;
 
$textorig=$text;
Line 209: Line 209:
 
</source>
 
</source>
 
For each anchor found we've to isolate the content of the next block.
 
For each anchor found we've to isolate the content of the next block.
{{#rawsnippetanchor: RawSnippet.php}}
+
{{#fileanchor: RawFile.php}}
 
<source lang=php>
 
<source lang=php>
 
foreach ($offsets as $offset) {
 
foreach ($offsets as $offset) {
Line 215: Line 215:
 
Let's remove the text up to the tag following the anchor
 
Let's remove the text up to the tag following the anchor
 
<br>'''TODO''': the next tag could be a < br >, which we should skip
 
<br>'''TODO''': the next tag could be a < br >, which we should skip
{{#rawsnippetanchor: RawSnippet.php}}
+
{{#fileanchor: RawFile.php}}
 
<source lang=php>
 
<source lang=php>
 
$out = substr($textorig, $offset[1]);
 
$out = substr($textorig, $offset[1]);
Line 223: Line 223:
 
<br>Note that we're looking to the word directly following '<' up to '>' or a space, e.g. if there are arguments to the tag.
 
<br>Note that we're looking to the word directly following '<' up to '>' or a space, e.g. if there are arguments to the tag.
 
<br>'''TODO''': once again, better handling of errors than just returning.
 
<br>'''TODO''': once again, better handling of errors than just returning.
{{#rawsnippetanchor: RawSnippet.php}}
+
{{#fileanchor: RawFile.php}}
 
<source lang=php>
 
<source lang=php>
 
if (!preg_match('/^<([^> ]+)/', $out, $matches))
 
if (!preg_match('/^<([^> ]+)/', $out, $matches))
Line 233: Line 233:
 
<br>We look for the closing tag and we take what's in between.
 
<br>We look for the closing tag and we take what's in between.
 
<br>'''TODO''': once again, better handling of errors than just returning.
 
<br>'''TODO''': once again, better handling of errors than just returning.
{{#rawsnippetanchor: RawSnippet.php}}
+
{{#fileanchor: RawFile.php}}
 
<source lang=php>
 
<source lang=php>
 
$begin = strpos($out, '>')+1;
 
$begin = strpos($out, '>')+1;
Line 247: Line 247:
 
Be nice with the browser and tell it how much data we'll send.
 
Be nice with the browser and tell it how much data we'll send.
 
<br>And that's it, $text contains our file!
 
<br>And that's it, $text contains our file!
{{#rawsnippetanchor: RawSnippet.php}}
+
{{#fileanchor: RawFile.php}}
 
<source lang=php>
 
<source lang=php>
 
header("Content-Length: ".strlen($text));
 
header("Content-Length: ".strlen($text));
Line 256: Line 256:
 
===Credits===
 
===Credits===
 
Let's say the extension is in the category of parser hooks even if there is also a hook on Raw action.
 
Let's say the extension is in the category of parser hooks even if there is also a hook on Raw action.
{{#rawsnippetanchor: RawSnippet.php}}
+
{{#fileanchor: RawFile.php}}
 
<source lang=php>
 
<source lang=php>
$wgExtensionCredits['parserhook'][] = array('name' => 'RawSnippet',
+
$wgExtensionCredits['parserhook'][] = array('name' => 'RawFile',
 
'version' => '0.1',
 
'version' => '0.1',
 
'author' => 'Philippe Teuwen',
 
'author' => 'Philippe Teuwen',
 
// 'url' => 'http://www.mediawiki.org/wiki/Extension:LocalServer',
 
// 'url' => 'http://www.mediawiki.org/wiki/Extension:LocalServer',
'url' => 'http://wiki.yobi.be/wiki/Mediawiki_RawSnippet',
+
'url' => 'http://wiki.yobi.be/wiki/Mediawiki_RawFile',
 
'description' => 'Downloads a RAW copy of <nowiki><tag>data</tag></nowiki> in a file<br>'.
 
'description' => 'Downloads a RAW copy of <nowiki><tag>data</tag></nowiki> in a file<br>'.
 
'Useful e.g. to download an example code or a patch<br>'.
 
'Useful e.g. to download an example code or a patch<br>'.
Line 272: Line 272:
   
 
==Installation==
 
==Installation==
Download [{{#rawsnippetlink: RawSnippet.php}} RawSnippet.php] and save it under the MediaWiki directory as extensions/RawSnippet/RawSnippet.php
+
Download [{{#filelink: RawFile.php}} RawFile.php] and save it under the MediaWiki directory as extensions/RawFile/RawFile.php
   
 
Add at the end of LocalSettings.php:
 
Add at the end of LocalSettings.php:
 
<source lang=php>
 
<source lang=php>
require_once("$IP/extensions/RawSnippet/RawSnippet.php");
+
require_once("$IP/extensions/RawFile/RawFile.php");
 
</source>
 
</source>

Revision as of 10:41, 5 April 2008

Very short introduction

Just have a look to the 2 examples to see how to use the extension
and to the Installation section to see how to install the extension in your MediaWiki server

Introduction

Originally the idea was to be able to download directly a portion of code as a file.
I've numerous code examples in my wiki and I wanted an easy way to download them, easier than a copy/paste!
But from there it was rather easy to get something very close to literate programming just by allowing multiple blocks referring to the same file, which will be concatenated together at download time.

  • It must work with pre, nowiki, js, css, code, source, so let's make it general: take the tag that comes after the parser function we'll create and select data up to the closing tag.
  • There are two distinct functionalities provided by the extension:
    • the parser that will convert a magic word into a link to the download URL
    • an extended ?action=raw that will strip the raw output to keep the desired code

Documentation used to create the extension

Syntax

There are 2 kinds of elements to add to the wiki language:

  • anchors that will flag which code blocks belong to a specific file
    • {{#fileAnchor: myscript.sh}}
    • Not visible in the regular wiki display
  • links that will allow to download the file
    • {{#fileLink: myscript.sh}}
    • Transformed into new regular wikicode that will be eventually transformed to real URLs:
      {{fullurl:{{PAGENAME}}|action=raw&file=myscript.sh}}
      http://wiki.yobi.be/index.php?title=Mediawiki_RawFile&action=raw&file=myscript.sh

For regular use, when a single code block is used and when the download link can be at the same position as the anchor, there is a shortcut notation mixing both anchor & link properties: {{#file: myscript.sh}}

Short example

Let's save the following code [{{#file: myscript.sh}} as myscript.sh]
<source lang=bash>
#!/bin/bash

echo 'Hello world!'
exit 0
</source>

will give:


Let's save the following code [{{#file: myscript.sh}} as myscript.sh]

#!/bin/bash

echo 'Hello world!'
exit 0

Complete example

And a full example with anchors & link:

Let's start with the Bash usual header:
{{#fileanchor: myotherscript.sh}}
<source lang=bash>
#!/bin/bash
</source>
Then we'll display a welcome message:
{{#fileanchor: myotherscript.sh}}
<source lang=bash>
echo 'Welcome on earth!'
exit 0
</source>
[{{#filelink: myotherscript.sh}} myotherscript.sh is now available for download below the code]

will give:


Let's start with the Bash usual header: {{#fileanchor: myotherscript.sh}}

#!/bin/bash

Then we'll display a welcome message: {{#fileanchor: myotherscript.sh}}

echo 'Welcome on earth!'
exit 0

[{{#filelink: myotherscript.sh}} myotherscript.sh is now available for download below the code]

The code

Which you can of course download just by following [{{#filelink: RawFile.php}} this link :-)]

So let's explain a bit the code in a Literate Programming way...

Hooks

First some hooks for our functions... {{#fileanchor: RawFile.php}}

<?php

if (defined('MEDIAWIKI')) {

# Define a setup function
$wgExtensionFunctions[] = 'efRawFile_Setup';
# Add a hook to initialise the magic words
$wgHooks['LanguageGetMagic'][]       = 'efRawFile_Magic';
# Add a hook to intercept the raw output
$wgHooks['RawPageViewBeforeOutput'][] = 'fnRawFile_Strip';

Setup function

For the wiki parsing to create download links, file and fileLink are equally treated, while fileAnchor will be simply left out. {{#fileanchor: RawFile.php}}

function efRawFile_Setup() {
    global $wgParser;
    # Set a function hook associating the "file" magic word with our function
    $wgParser->setFunctionHook( 'file', 'efRawFile_Render' );
    $wgParser->setFunctionHook( 'filelink', 'efRawFile_Render' );
    $wgParser->setFunctionHook( 'fileanchor', 'efRawFile_Empty' );
}

Hook to initialize the magic words

{{#fileanchor: RawFile.php}}

function efRawFile_Magic( &$magicWords, $langCode ) {
    # Add the magic word
    # The first array element is case sensitive, in this case it is not case sensitive
    # All remaining elements are synonyms for our parser function
    $magicWords['file'] = array( 0, 'file' );
    $magicWords['filelink'] = array( 0, 'filelink' );
    $magicWords['fileanchor'] = array( 0, 'fileanchor' );
    # unless we return true, other parser functions extensions will not get loaded.
    return true;
}

Parser functions of the magic words

The transformation rule to replace link shortcuts to actual links for download
TODO: what error to send out if there is no filename given?
TODO: supports links to files located in other local wiki pages, sth like 2nd arg default to $pagename='Mediawiki RawFile' {{#fileanchor: RawFile.php}}

function efRawFile_Render( &$parser, $filename = '') {
    # The parser function itself
    # The input parameters are wikitext with templates expanded
    # The output should be wikitext too
    return '{{fullurl:{{PAGENAME}}|action=raw&file='.$filename.'}}';
}

And the other one, just removing the anchors from the rendered wiki page.
Curiously enough if the function doesn't exist at all the effect is exactly the same, MW doesn't throw any error.
But let's keep things clean... {{#fileanchor: RawFile.php}}

function efRawFile_Empty( &$parser, $filename = '') {
    return '';
}

Hook to intercept the raw output

This part of the code doesn't look that nice because we've to parse the raw wiki page ourselves to retrieve the code sections we want.

First let's see if ?action=raw was used in the context of this extension: in that case we receive the filename as GET parameter, otherwise we simply return from our extension with return value=true which means we authorize the raw display (originally the hook was created to add an authentication point) {{#fileanchor: RawFile.php}}

function fnRawFile_Strip(&$rawPage, &$text) {
    if (!isset($_GET['file']))
        return true;
    $filename=$_GET['file'];

Raw action already set the headers with some client cache pragmas and is supposed to be displayed in the browser but in our case we want to make this "page" a downloadable file so we overwrite the headers which were defined and we add a few more, to ensure there is no caching on the client (it's very hard for the client to force a refresh on a file download, contrary to a web page) and to provide the adequate filename.
At the end once we know the size of the data we'll transfer, we'll add a Content-Length header {{#fileanchor: RawFile.php}}

    header("Content-disposition:filename=".$filename);
    header("Content-type:application/octetstream"); 
    header("Content-Transfer-Encoding: binary"); 
    header("Expires: 0");
    header("Pragma: no-cache"); 
    header("Cache-Control: no-store");

Then we'll strip the output, first we've to locate the anchors but there are anchors that could be protected in literal blocks like nowiki.
So we'll mask the literal blocks before searching for the anchors (we mask with the same string length because we'll retrieve an offset that we will use on the initial string and offsets must match)
TODO: should we care also of source, js, css, pre,... blocks? {{#fileanchor: RawFile.php}}

    $maskedtext=preg_replace_callback('/<nowiki>(.*?)<\/nowiki>/', 
        create_function(
           '$matches',
           'return ereg_replace(".","X",$matches[0]);'
        ),
        $text);

Now we can search for the anchors (or the short version, in which case we only keep the first hit, no multiple blocks support)
And we free the memory used for the masked version
TODO: instead of cowardly returning if we don't find our anchors, we should cancel the headers and return a proper error page {{#fileanchor: RawFile.php}}

    if (preg_match_all('/{{#fileanchor: +'.$filename.' *}}/i', $maskedtext, $matches, PREG_OFFSET_CAPTURE))
        $offsets=$matches[0];
    else if (preg_match_all('/{{#file: +'.$filename.' *}}/i', $maskedtext, $matches, PREG_OFFSET_CAPTURE))
        $offsets=array($matches[0][0]);
    else
        // We didn't find our anchor, let's output all the raw...
        return true;
    unset($maskedtext);

$text is both input & output so we copy it and start with an empty output. {{#fileanchor: RawFile.php}}

    $textorig=$text;
    $text='';

For each anchor found we've to isolate the content of the next block. {{#fileanchor: RawFile.php}}

    foreach ($offsets as $offset) {

Let's remove the text up to the tag following the anchor
TODO: the next tag could be a < br >, which we should skip {{#fileanchor: RawFile.php}}

        $out = substr($textorig, $offset[1]);
        $out = substr($out, strpos($out, '<'));

What type of tag do we have?
Note that we're looking to the word directly following '<' up to '>' or a space, e.g. if there are arguments to the tag.
TODO: once again, better handling of errors than just returning. {{#fileanchor: RawFile.php}}

        if (!preg_match('/^<([^> ]+)/', $out, $matches))
            return true;
        $key = $matches[1];

OK, let's extract the text up to the closing tag
We skip the first carriage return after the opening tag, if any
We look for the closing tag and we take what's in between.
TODO: once again, better handling of errors than just returning. {{#fileanchor: RawFile.php}}

        $begin = strpos($out, '>')+1;
        if (ord(substr($out,$begin,1))==10)
            $begin++;
        if (preg_match_all('/<\/'.$key.'>/', $out, $matches, PREG_OFFSET_CAPTURE))
            $text .= substr($out, $begin, $matches[0][0][1]-$begin);
        else
            // error, we could not find end of bloc
            $text .= substr($out, $begin);
    }

Be nice with the browser and tell it how much data we'll send.
And that's it, $text contains our file! {{#fileanchor: RawFile.php}}

    header("Content-Length: ".strlen($text)); 
    return true;
}

Credits

Let's say the extension is in the category of parser hooks even if there is also a hook on Raw action. {{#fileanchor: RawFile.php}}

$wgExtensionCredits['parserhook'][] = array('name' => 'RawFile',
                           'version' => '0.1',
                           'author' => 'Philippe Teuwen',
//                         'url' => 'http://www.mediawiki.org/wiki/Extension:LocalServer',
                           'url' => 'http://wiki.yobi.be/wiki/Mediawiki_RawFile',
                           'description' => 'Downloads a RAW copy of <nowiki><tag>data</tag></nowiki> in a file<br>'.
                                            'Useful e.g. to download an example code or a patch<br>'.
                                            'It also opens the path to [http://en.wikipedia.org/wiki/Literate_programming Literate Programming]');
}

?>

Installation

Download [{{#filelink: RawFile.php}} RawFile.php] and save it under the MediaWiki directory as extensions/RawFile/RawFile.php

Add at the end of LocalSettings.php:

require_once("$IP/extensions/RawFile/RawFile.php");