Difference between revisions of "Mediawiki RawFile"

From YobiWiki
Jump to navigation Jump to search
Line 51: Line 51:
 
* Tells the browser NOT to cache the raw
 
* Tells the browser NOT to cache the raw
 
==The code==
 
==The code==
Which you can of course download just by following [{{#rawsnippet: RawSnippet.php}} this link :-)]
+
Which you can of course download just by following [{{#rawsnippetlink: RawSnippet.php}} this link :-)]
  +
  +
So let's explain a bit the code in a Literate Programming way...
  +
  +
First some hooks for our functions...
  +
{{#rawsnippetanchor: RawSnippet.php}}
 
<source lang=php>
 
<source lang=php>
 
<?php
 
<?php
Line 64: Line 69:
 
$wgHooks['RawPageViewBeforeOutput'][] = 'fnRawSnippet_Strip';
 
$wgHooks['RawPageViewBeforeOutput'][] = 'fnRawSnippet_Strip';
   
  +
</source>
  +
Then the function to strip the code out of the raw wiki page, quite heavy as we've to parse the wiki page ourselves...
  +
{{#rawsnippetanchor: RawSnippet.php}}
  +
<source lang=php>
 
function fnRawSnippet_Strip(&$rawPage, &$text) {
 
function fnRawSnippet_Strip(&$rawPage, &$text) {
  +
// if our ext wasn't used, just exit
 
if (!isset($_GET['rawsnippet']))
 
if (!isset($_GET['rawsnippet']))
 
return true;
 
return true;
 
$filename=$_GET['rawsnippet'];
 
$filename=$_GET['rawsnippet'];
  +
// debug:
  +
if (0) {
 
header("Content-disposition:filename=".$filename);
 
header("Content-disposition:filename=".$filename);
 
header("Content-type:application/octetstream");
 
header("Content-type:application/octetstream");
header("Expires: 0");
 
 
header("Content-Transfer-Encoding: binary");
 
header("Content-Transfer-Encoding: binary");
  +
} else {
  +
header("Content-type:text/plain");
 
}
 
header("Expires: 0");
 
header("Pragma: no-cache");
 
header("Pragma: no-cache");
 
header("Cache-Control: no-store");
 
header("Cache-Control: no-store");
 
// First: find the right anchor
 
// First: find the right anchor
 
// We mask nowiki sections
 
// We mask nowiki sections
  +
//TODO also mask source, js, css, pre, what else?
$text2=preg_replace_callback('/<nowiki>(.*?)<\/nowiki>/',
 
  +
//test2 will contain interpretable content, all static content is blanked out
 
$maskedtext=preg_replace_callback('/<nowiki>(.*?)<\/nowiki>/',
 
create_function(
 
create_function(
 
'$matches',
 
'$matches',
Line 83: Line 100:
 
$text);
 
$text);
 
// We search rawsnippet anchor position
 
// We search rawsnippet anchor position
if (preg_match_all('/{{#rawsnippetanchor: +'.$filename.' *}}/i', $text2, $matches, PREG_OFFSET_CAPTURE))
+
if (preg_match_all('/{{#rawsnippetanchor: +'.$filename.' *}}/i', $maskedtext, $matches, PREG_OFFSET_CAPTURE))
$offset=$matches[0][0][1];
+
$offsets=$matches[0];
else if (preg_match_all('/{{#rawsnippet: +'.$filename.' *}}/i', $text2, $matches, PREG_OFFSET_CAPTURE))
+
else if (preg_match_all('/{{#rawsnippet: +'.$filename.' *}}/i', $maskedtext, $matches, PREG_OFFSET_CAPTURE))
  +
// If the shortcut "rawsnippet" is used, no nuweb, just the first hit is considered
$offset=$matches[0][0][1];
+
$offsets=array($matches[0][0]);
 
else
 
else
 
// We didn't find our anchor, let's output all the raw...
 
// We didn't find our anchor, let's output all the raw...
  +
// TODO change headers & send error msg
 
return true;
 
return true;
  +
// free some mem
// Now let's remove the text up to our anchor
 
$text = substr($text, $offset);
+
unset($maskedtext);
  +
$textorig=$text;
// What's the type of tag do we have?
 
$text = substr($text, strpos($text, '<'));
+
$text='';
  +
foreach ($offsets as $offset) {
if (!preg_match('/^<([^> ]+)/', $text, $matches))
 
 
// Now let's remove the text up to our anchor
return true;
 
$key = $matches[1];
+
$out = substr($textorig, $offset[1]);
// Let's extract the text up to the closing tag
+
// What's the type of tag do we have?
$begin = strpos($text, '>')+1;
+
$out = substr($out, strpos($out, '<'));
if (ord(substr($text,$begin,1))==10)
+
if (!preg_match('/^<([^> ]+)/', $out, $matches))
  +
// TODO send error, we could not find end of bloc
$begin++;
 
 
return true;
if (preg_match_all('/<\/'.$key.'>/', $text, $matches, PREG_OFFSET_CAPTURE))
 
$text = substr($text, $begin, $matches[0][0][1]-$begin);
+
$key = $matches[1];
  +
// Let's extract the text up to the closing tag
else
 
$text = substr($text, $begin);
+
$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
  +
// TODO send error, we could not find end of bloc
  +
$text .= substr($out, $begin);
  +
}
  +
 
header("Content-Length: ".strlen($text));
 
header("Content-Length: ".strlen($text));
 
return true;
 
return true;
 
//TODO: downloadAs..
 
//TODO: downloadAs..
 
}
 
}
  +
</source>
 
  +
That's again part of the hooks...
  +
{{#rawsnippetanchor: RawSnippet.php}}
  +
<source lang=php>
 
function efRawSnippet_Setup() {
 
function efRawSnippet_Setup() {
 
global $wgParser;
 
global $wgParser;
Line 117: Line 148:
 
$wgParser->setFunctionHook( 'rawsnippetanchor', 'efRawSnippet_Empty' );
 
$wgParser->setFunctionHook( 'rawsnippetanchor', 'efRawSnippet_Empty' );
 
}
 
}
  +
</source>
 
  +
This part as well...
  +
{{#rawsnippetanchor: RawSnippet.php}}
  +
<source lang=php>
 
function efRawSnippet_Magic( &$magicWords, $langCode ) {
 
function efRawSnippet_Magic( &$magicWords, $langCode ) {
 
# Add the magic word
 
# Add the magic word
Line 128: Line 162:
 
return true;
 
return true;
 
}
 
}
  +
</source>
 
  +
The transformation rule to replace link shortcuts to actual links for download
  +
{{#rawsnippetanchor: RawSnippet.php}}
  +
<source lang=php>
 
function efRawSnippet_Render( &$parser, $filename = '') {
 
function efRawSnippet_Render( &$parser, $filename = '') {
 
# The parser function itself
 
# The parser function itself
Line 136: Line 173:
 
//TODO+support for other pages
 
//TODO+support for other pages
 
}
 
}
  +
</source>
function efRawSnippet_Empty( &$parser, $filename = '') {
 
  +
Credits :-)
return '';
 
  +
{{#rawsnippetanchor: RawSnippet.php}}
}
 
  +
<source lang=php>
 
 
$wgExtensionCredits['parserhook'][] = array('name' => 'RawSnippet',
 
$wgExtensionCredits['parserhook'][] = array('name' => 'RawSnippet',
 
'version' => '0.1',
 
'version' => '0.1',
Line 146: Line 183:
 
'url' => 'http://wiki.yobi.be/wiki/Mediawiki_RawSnippet',
 
'url' => 'http://wiki.yobi.be/wiki/Mediawiki_RawSnippet',
 
'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');
+
'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]');
 
}
 
}
   

Revision as of 15:01, 3 April 2008

Introduction

The idea is to be able to download directly a portion of code as a file.
I've numerous code examples in my wiki and I wast an easy way to download them, easier than a copy/paste!

  • 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.

2 parts:

  • the parser magic word that will be converted into a "Save it as <filename>"
  • an extended action=raw that will strip the raw output to keep the desired code

Documentation

Syntax

{{#rawsnippet: myscript.sh}}
Do we need a MIME type?
Transformation:
{{fullurl:{{PAGENAME}}|action=raw&rawsnippet=myscript.sh}}
Test: save the following code [{{#rawsnippet: myscript.sh}} as myscript.sh]

#!/bin/bash

echo 'Hello world!'
exit 0

{{#rawsnippet: myscript.sh}} combines in fact 2 elements: the text that will be replaced by the link and the anchor just before the code section.
We can separate both functionalities with:

One such declaration, just before the code section: {{#rawsnippetAnchor: myscript.sh}}

One or many such declarations to create the download links: {{#rawsnippetLink: myscript.sh}}

Example: {{#rawsnippetanchor: myotherscript.sh}}

#!/bin/bash

echo 'Hello earth!'
exit 0

[{{#rawsnippetlink: myotherscript.sh}} myotherscript.sh is available now below the code]

Hook on Raw

  • Must extract the right paragraph
    • Strip all up to the right rawsnippet: filename tag
    • Find the next tag
    • Select up to the closure tag
  • Must provide the filename to the browser
  • Tells the browser NOT to cache the raw

The code

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

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

First some hooks for our functions... {{#rawsnippetanchor: RawSnippet.php}}

<?php

if (defined('MEDIAWIKI')) {

# Define a setup function
$wgExtensionFunctions[] = 'efRawSnippet_Setup';
# Add a hook to initialise the magic word
$wgHooks['LanguageGetMagic'][]       = 'efRawSnippet_Magic';
# Add a hook to intercept the raw output
$wgHooks['RawPageViewBeforeOutput'][] = 'fnRawSnippet_Strip';

Then the function to strip the code out of the raw wiki page, quite heavy as we've to parse the wiki page ourselves... {{#rawsnippetanchor: RawSnippet.php}}

function fnRawSnippet_Strip(&$rawPage, &$text) {
    // if our ext wasn't used, just exit
    if (!isset($_GET['rawsnippet']))
        return true;
    $filename=$_GET['rawsnippet'];
// debug:
if (0) {
    header("Content-disposition:filename=".$filename);
    header("Content-type:application/octetstream"); 
    header("Content-Transfer-Encoding: binary"); 
} else {
    header("Content-type:text/plain"); 
}
    header("Expires: 0");
    header("Pragma: no-cache"); 
    header("Cache-Control: no-store");
    // First: find the right anchor
    // We mask nowiki sections
//TODO also mask source, js, css, pre, what else?
    //test2 will contain interpretable content, all static content is blanked out
    $maskedtext=preg_replace_callback('/<nowiki>(.*?)<\/nowiki>/', 
        create_function(
           '$matches',
           'return ereg_replace(".","X",$matches[0]);'
        ),
        $text);
    // We search rawsnippet anchor position
    if (preg_match_all('/{{#rawsnippetanchor: +'.$filename.' *}}/i', $maskedtext, $matches, PREG_OFFSET_CAPTURE))
        $offsets=$matches[0];
    else if (preg_match_all('/{{#rawsnippet: +'.$filename.' *}}/i', $maskedtext, $matches, PREG_OFFSET_CAPTURE))
        // If the shortcut "rawsnippet" is used, no nuweb, just the first hit is considered
        $offsets=array($matches[0][0]);
    else
        // We didn't find our anchor, let's output all the raw...
// TODO change headers & send error msg
        return true;
    // free some mem
    unset($maskedtext);
    $textorig=$text;
    $text='';
    foreach ($offsets as $offset) {
        // Now let's remove the text up to our anchor
        $out = substr($textorig, $offset[1]);
        // What's the type of tag do we have?
        $out = substr($out, strpos($out, '<'));
        if (!preg_match('/^<([^> ]+)/', $out, $matches))
// TODO send error, we could not find end of bloc
            return true;
        $key = $matches[1];
        // Let's extract the text up to the closing tag
        $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
// TODO send error, we could not find end of bloc
            $text .= substr($out, $begin);
    }

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

That's again part of the hooks... {{#rawsnippetanchor: RawSnippet.php}}

function efRawSnippet_Setup() {
    global $wgParser;
    # Set a function hook associating the "rawsnippet" magic word with our function
    $wgParser->setFunctionHook( 'rawsnippet', 'efRawSnippet_Render' );
    $wgParser->setFunctionHook( 'rawsnippetlink', 'efRawSnippet_Render' );
    $wgParser->setFunctionHook( 'rawsnippetanchor', 'efRawSnippet_Empty' );
}

This part as well... {{#rawsnippetanchor: RawSnippet.php}}

function efRawSnippet_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['rawsnippet'] = array( 0, 'rawsnippet', 'downloadAs' );
    $magicWords['rawsnippetlink'] = array( 0, 'rawsnippetlink', 'downloadLink' );
    $magicWords['rawsnippetanchor'] = array( 0, 'rawsnippetanchor', 'downloadAnchor' );
    # unless we return true, other parser functions extensions will not get loaded.
    return true;
}

The transformation rule to replace link shortcuts to actual links for download {{#rawsnippetanchor: RawSnippet.php}}

function efRawSnippet_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&rawsnippet='.$filename.'}}';
//TODO+support for other pages
}

Credits :-) {{#rawsnippetanchor: RawSnippet.php}}

$wgExtensionCredits['parserhook'][] = array('name' => 'RawSnippet',
                           'version' => '0.1',
                           'author' => 'Philippe Teuwen',
//                         'url' => 'http://www.mediawiki.org/wiki/Extension:LocalServer',
                           'url' => 'http://wiki.yobi.be/wiki/Mediawiki_RawSnippet',
                           '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]');
}

?>