Features:
What is the benefit? Subpages were never designed to support proper namespaces; with Shatter, you can get them.
Examples:
This is a different way of thinking about subpages, so I suggest the following rules to abide by:
This patch is a spike of the MeatBall:FacetWiki project; see that page for more information.
-- KritTer
@@ -45,7 +45,7 @@
$UrlProtocols $UrlPattern $ImageExtensions $RFCPattern $ISBNPattern $FS $FS1 $FS2 $FS3 $CookieName $SiteBase $StyleSheet $NotFoundPg $FooterNote $EditNote $MaxPost $NewText $NotifyDefault $HttpCharset $UserGotoBar $UseSetpage $ShatterWiki $SetDivChar $SetFSChar ); # Note: $NotifyDefault is kept because it was a config variable in 0.90 # Other global variables: use vars qw(%Page %Section %Text %InterSite %SaveUrl %SaveNumUrl@@ -84,9 +84,12 @@
$NewText = ""; # New page text ("" for default message)
$HttpCharset = ""; # Charset for pages, like "iso-8859-2"
$UserGotoBar = ""; # HTML added to end of goto bar
$SetDivChar = "/"; # Set divider when $UseSetpage active
$SetFSChar = ","; # Set divider to use in file storage
# Major options:
$UseSubpage = 1; # 1 = use subpages, 0 = do not use subpages
$UseSetpage = 1; # 1 = use setsubpages, 0 = do not use setsubpages
$UseCache = 0; # 1 = cache HTML pages, 0 = generate every page
$EditAllowed = 1; # 1 = editing allowed, 0 = read-only
$RawHtml = 0; # 1 = allow <HTML> tag, 0 = no raw HTML in pages
@@ -119,6 +122,7 @@
$UseLookup = 1; # 1 = lookup host names, 0 = skip lookup (IP only) $FreeUpper = 1; # 1 = force upper case, 0 = do not force case $FastGlob = 1; # 1 = new faster code, 0 = old compatible code $ShatterWiki = 1; # 1 = use Shatter algo, 0 = no Shattering # HTML tag lists, enabled if $HtmlTags is set. # Scripting is currently possible with these tags,@@ -161,7 +165,7 @@
# == Common and cache-browsing code ====================================
sub InitLinkPatterns {
my ($UpperLetter, $LowerLetter, $AnyLetter, $LpA, $LpB, $LpX, $QDelim);
# Field separators are used in the URL-style patterns below.
$FS = "\xb3"; # The FS character is a superscript "3"
@@ -185,14 +189,17 @@
# Main link pattern: lowercase between uppercase, then anything
$LpA = $UpperLetter . "+" . $LowerLetter . "+" . $UpperLetter
. $AnyLetter . "*";
# Optional loose subpage link pattern: uppercase, lowercase, then anything
$LpB = $UpperLetter . "+" . $LowerLetter . "+" . $AnyLetter . "*";
# Strict pattern: use main LinkPattern for subpages
# $LpB = $LpA;
# Optional set-subpage link pattern: subpage names separated by set divider
my $LpX = "$LpB(?:[$SetDivChar]$LpB)*";
if ($UseSubpage and $UseSetpage) {
$LinkPattern = "((?:(?:$LpA)?\\/$LpX)|$LpA)";
} elsif ($UseSubpage) {
$LinkPattern = "((?:(?:$LpA)?\\/$LpB)|$LpA)";
} else {
$LinkPattern = "($LpA)";
}
@@ -213,7 +220,10 @@
}
}
$FreeLinkPattern = "($AnyLetter+)";
if ($UseSetpage and $UseSubpage) {
$FreeLinkPattern = "((?:(?:$AnyLetter+)?\\/(?:$AnyLetter+" .
"[$SetDivChar])*)?$AnyLetter+)";
} elsif ($UseSubpage) {
$FreeLinkPattern = "((?:(?:$AnyLetter+)?\\/)?$AnyLetter+)";
}
$FreeLinkPattern .= $QDelim;
@@ -264,7 +274,7 @@
sub GetHtmlCacheFile {
my ($id) = @_;
return $HtmlDir . "/" . &GetPage($id) . ".htm";
}
sub GetPageDirectory {
@@ -360,6 +370,9 @@
if ($FreeLinks && (!-f &GetPageFile($id))) {
$id = &FreeToNormal($id);
}
if ($UseSetpage) {
$id = &SetToNormal($id);
}
if (($NotFoundPg ne '') && (!-f &GetPageFile($id))) {
$id = $NotFoundPg;
}
@@ -372,6 +385,9 @@
if ($FreeLinks && (!-f &GetPageFile($id))) {
$id = &FreeToNormal($id);
}
if ($UseSetpage) {
$id = &SetToNormal($id);
}
if (($NotFoundPg ne '') && (!-f &GetPageFile($id))) {
$id = $NotFoundPg;
}
@@ -384,6 +400,9 @@
&DoRandom();
return 1;
} elsif ($action eq 'history') {
if ($UseSetpage) {
$id = &SetToNormal($id);
}
&DoHistory($id) if &ValidIdOrDie($id);
return 1;
}
@@ -421,6 +440,9 @@
} else {
($id) = ($Text{'text'} =~ /\#REDIRECT\s+(\S+)/);
}
if ($UseSetpage) {
$id = &SetToNormal($id);
}
if (&ValidId($id) eq '') {
# Later consider revision in rebrowse?
&ReBrowsePage($id, $oldId, 0);
@@ -460,7 +482,7 @@
&OpenKeptRevisions('text_default') if (!$openKept);
$fullHtml .= &GetDiffHTML($showDiff, $id, $diffRevision, $newText);
}
$fullHtml .= &WikiToHTML($Text{'text'}, $id);
$fullHtml .= "<hr>\n" if (!&GetParam('embed', $EmbedWiki));
if (($id eq $RCName) || (T($RCName) eq $id) || (T($id) eq $RCName)) {
print $fullHtml;
@@ -747,9 +769,9 @@
$html = Ts('Revision %s', $rev) . ": ";
if ($isCurrent) {
$html .= &GetPageLinkText($id, T('View'), '') . ' ';
if ($canEdit) {
$html .= &GetEditLink($id, T('Edit'), '') . ' ';
}
if ($UseDiff) {
$html .= T('Diff') . ' ';
@@ -793,13 +815,16 @@
}
sub GetPageLinkText {
my ($id, $name, $ref) = @_;
$id =~ s|^/|$MainPage/|;
if ($FreeLinks) {
$id = &FreeToNormal($id);
$name =~ s/_/ /g;
}
if ($ShatterWiki and $ref) {
$id .= ":".$ref;
}
return &ScriptLink($id, $name);
}
@@ -824,7 +849,7 @@
}
sub GetPageOrEditLink {
my ($id, $name, $ref) = @_;
my (@temp, $exists);
if ($name eq "") {
@@ -836,6 +861,11 @@
$id =~ s|^/|$MainPage/|;
if ($FreeLinks) {
$id = &FreeToNormal($id);
$ref = &FreeToNormal($ref);
}
if ($UseSetpage) {
$id = &SetToNormal($id);
$ref = &SetToNormal($ref);
}
$exists = 0;
if ($UseIndex) {
@@ -847,7 +877,7 @@
$exists = 1;
}
if ($exists) {
return &GetPageLinkText($id, $name, $ref);
}
if ($FreeLinks) {
if ($name =~ m| |) { # Not a single word
@@ -1041,7 +1071,7 @@
$result .= &GetHistoryLink($id, T('View other revisions'));
if ($rev ne '') {
$result .= ' | ';
$result .= &GetPageLinkText($id, T('View current revision'), '');
}
if ($Section{'revision'} > 0) {
$result .= '<br>';
@@ -1151,7 +1181,7 @@
# ==== Common wiki markup ====
sub WikiToHTML {
my ($pageText, $id) = @_;
%SaveUrl = ();
%SaveNumUrl = ();
@@ -1163,15 +1193,15 @@
}
$pageText = &QuoteHtml($pageText);
$pageText =~ s/\\ *\r?\n/ /g; # Join lines with backslash at end
$pageText = &CommonMarkup($pageText, 1, 0, $id); # Multi-line markup
$pageText = &WikiLinesToHtml($pageText, $id); # Line-oriented markup
$pageText =~ s/$FS(\d+)$FS/$SaveUrl{$1}/ge; # Restore saved text
$pageText =~ s/$FS(\d+)$FS/$SaveUrl{$1}/ge; # Restore nested saved text
return $pageText;
}
sub CommonMarkup {
my ($text, $useImage, $doLines, $id) = @_;
local $_ = $text;
if ($doLines < 2) { # 2 = do line-oriented only
@@ -1202,8 +1232,9 @@
if ($FreeLinks) {
# Consider: should local free-link descriptions be conditional?
# Also, consider that one could write [[Bad Page|Good Page]]?
s/\[\[$FreeLinkPattern\|([^\]]+)\]\]/&StorePageOrEditLink($1,$2,$id)
/geo;
s/\[\[$FreeLinkPattern\]\]/&StorePageOrEditLink($1, "", $id)/geo;
}
if ($BracketText) { # Links like [URL text of link]
s/\[$UrlPattern\s+([^\]]+?)\]/&StoreBracketUrl($1, $2)/geos;
@@ -1217,7 +1248,7 @@
s/$UrlPattern/&StoreUrl($1, $useImage)/geo;
s/$InterLinkPattern/&StoreInterPage($1)/geo;
if ($WikiLinks) {
s/$LinkPattern/&GetPageOrEditLink($1, "", $id)/geo;
}
s/$RFCPattern/&StoreRFC($1)/geo;
s/$ISBNPattern/&StoreISBN($1)/geo;
@@ -1241,7 +1272,7 @@
}
sub WikiLinesToHtml {
my ($pageText, $id) = @_;
my ($pageHtml, @htmlStack, $code, $depth, $oldCode);
@htmlStack = ();
@@ -1284,8 +1315,8 @@
}
}
s/^\s*$/<p>\n/; # Blank lines become <p> tags
$pageHtml .= &CommonMarkup($_, 1, 2, $id); # Line-oriented common markup
}
while (@htmlStack > 0) { # Clear stack
$pageHtml .= "</" . pop(@htmlStack) . ">\n";
@@ -1434,11 +1465,11 @@
sub StoreBracketLink {
my ($name, $text) = @_;
return &StoreRaw(&GetPageLinkText($name, "[$text]", ''));
}
sub StorePageOrEditLink {
my ($page, $name, $id) = @_;
if ($FreeLinks) {
$page =~ s/^\s+//; # Trim extra spaces
@@ -1447,7 +1478,7 @@
}
$name =~ s/^\s+//;
$name =~ s/\s+$//;
return &StoreRaw(&GetPageOrEditLink($page, $name, $id));
}
sub StoreRFC {
@@ -1588,7 +1619,7 @@
if ($rev ne "") {
$html = '<b>'
. Ts('Difference (from revision %s to current revision)', $rev)
. "</b>\n" . "$links<br>" . &DiffToHTML($diffText,$id) . "<hr>\n";
} else {
if (($diffType != 2) &&
((!defined(&GetPageCache("old$cacheName"))) ||
@@ -1599,7 +1630,7 @@
} else {
$html = '<b>'
. Ts('Difference (from prior %s revision)', $priorName)
. "</b>\n$links<br>" . &DiffToHTML($diffText,$id) . "<hr>\n";
}
}
return $html;
@@ -1652,7 +1683,7 @@
}
sub DiffToHTML {
my ($html, $id) = @_;
my ($tChanged, $tRemoved, $tAdded);
$tChanged = T('Changed:');
@@ -1663,13 +1694,13 @@
$html =~ s/(^|\n)(\d+.*c.*)/$1 <br><strong>$tChanged $2<\/strong><br>/g;
$html =~ s/(^|\n)(\d+.*d.*)/$1 <br><strong>$tRemoved $2<\/strong><br>/g;
$html =~ s/(^|\n)(\d+.*a.*)/$1 <br><strong>$tAdded $2<\/strong><br>/g;
$html =~ s/\n((<.*\n)+)/&ColorDiff($1,"ffffaf",$id)/ge;
$html =~ s/\n((>.*\n)+)/&ColorDiff($1,"cfffcf",$id)/ge;
return $html;
}
sub ColorDiff {
my ($diff, $color, $id) = @_;
$diff =~ s/(^|\n)[<>]/$1/g;
$diff = &QuoteHtml($diff);
@@ -1679,7 +1710,7 @@
$SaveUrlIndex = 0;
$SaveNumUrlIndex = 0;
$diff =~ s/$FS//g;
$diff = &CommonMarkup($diff, 0, 1, $id); # No images, all patterns
$diff =~ s/$FS(\d+)$FS/$SaveUrl{$1}/ge; # Restore saved text
$diff =~ s/$FS(\d+)$FS/$SaveUrl{$1}/ge; # Restore nested saved text
$diff =~ s/\r?\n/<br>/g;
@@ -1734,7 +1765,7 @@
sub GetPageFile {
my ($id) = @_;
return $PageDir . "/" . &GetPage($id) . ".db";
}
sub OpenPage {
@@ -1841,8 +1872,7 @@
}
sub KeepFileName {
return $KeepDir . "/" . &GetPage($OpenPageName) . ".kp";
}
sub SaveKeepSection {
@@ -1982,7 +2012,17 @@
if ($id =~ m| |) {
return Ts('Page name may not contain space characters: %s', $id);
}
if ($UseSetpage and $UseSubpage) {
if (($SetDivChar ne "/") and ($id =~ m|.*/.*/|)) {
return Ts('Too many / characters in page %s', $id);
}
if (($id =~ /^\//) or ($id =~ m/^[$SetDivChar]/)) {
return Ts('Invalid Page %s (subpage without main page)', $id);
}
if (($id =~ /\/$/) or ($id =~ /[$SetDivChar]$/)) {
return Ts('Invalid Page %s (missing subpage name)', $id);
}
} elsif ($UseSubpage) {
if ($id =~ m|.*/.*/|) {
return Ts('Too many / characters in page %s', $id);
}
@@ -2099,7 +2139,7 @@
sub GetLockedPageFile {
my ($id) = @_;
return $PageDir . "/" . &GetPage($id) . ".lck";
}
sub RequestLockDir {
@@ -2422,6 +2462,10 @@
$id =~ s/__+/_/g;
$id =~ s/^_//;
$id =~ s/_$//;
if ($UseSetpage and $UseSubpage) {
$id =~ s|_\\${SetDivChar}|${SetDivChar}|g;
$id =~ s|\\${SetDivChar}_|${SetDivChar}|g;
}
if ($UseSubpage) {
$id =~ s|_/|/|g;
$id =~ s|/_|/|g;
@@ -2435,6 +2479,59 @@
}
return $id;
}
sub SetToNormal {
(my $id) = @_;
return $id unless $id =~ m|[:/]|;
if ($id =~ s(:(.*))()) {
#### Run the Shatter algorithm ###############################################
my $referrer = SetToNormal($1);
OpenPage($1);
OpenDefaultText();
my @LINKS = ($Text{'text'} =~ m($LinkPattern)g);
push @LINKS, (grep /^$LinkPattern$/, split "/", $referrer);
(my $name, my $set) = split "/", $id, 2;
my @set = sort split /$SetDivChar/, $set;
my %set = map { $_ => undef } @set;
my $ss = "\./" . $PageDir . "/" . &GetPageDirectory($name) ."/".$name."/";
$ss .= "\\(.*[$SetFSChar]\\)?" . join("[$SetFSChar]",
map { $_ . "\\([$SetFSChar].*\\)?" } @set) if (scalar @set);
$ss .= ".*" unless (scalar @set);
$ss .= "\.db";
my @VALID = grep { scalar(@set) == scalar(grep { exists $set{$_} }
split $SetFSChar, $_) }
map { m(([^/]*)\.db$); $1 } split "\n", `find . -regex "$ss"`;
push @VALID, join $SetFSChar, @set;
my %VALIDWORDS = map { ($_ => undef) }
map { split $SetFSChar, $_ } @VALID;
my %WORDS = map { ($_ => undef) } grep { not exists $set{$_} }
grep { exists $VALIDWORDS{$_} } @LINKS;
my %SHATTER;
my @APPLICABLE = map { map { $SHATTER{$_}++ } split $SetFSChar, $_; $_ }
grep { scalar(keys %WORDS) == scalar
grep { exists $WORDS{$_} }
split $SetFSChar, $_ } @VALID;
my $SHATTER = join $SetFSChar, sort grep
{ $SHATTER{$_} == scalar @APPLICABLE } keys %SHATTER;
if ((0 < scalar @APPLICABLE) and
(0 < grep { $_ eq $SHATTER } @APPLICABLE)) {
$SHATTER =~ s([$SetFSChar])($SetDivChar);
return $name unless $SHATTER;
return "$name/$SHATTER";
}
return $name if scalar @set == 0;
return "$name/".join $SetDivChar, @set;
}
(my $name, my $set) = split "/", $id, 2;
$set = join $SetDivChar, sort split /$SetDivChar/, $set;
return "$name/$set";
}
sub GetPage {
(my $id) = @_;
return &GetPageDirectory($id)."/".$id unless $id =~ m|/|;
(my $name, my $set) = split "/", $id, 2;
$set = join $SetFSChar, split /$SetDivChar/, $set;
return GetPageDirectory($id)."/$name/$set";
}
#END_OF_BROWSE_CODE
# == Page-editing and other special-action code ========================
@@ -2632,7 +2729,7 @@
}
$MainPage = $id;
$MainPage =~ s|/.*||; # Only the main page name (remove subpage)
print &WikiToHTML($oldText, $id) . "<hr>\n";
print "<h2>", T('Preview only, not yet saved'), "</h2>\n";
}
print &GetHistoryLink($id, T('View other revisions')) . "<br>\n";
@@ -3806,7 +3903,7 @@
$fname = &GetPageFile($page); unlink($fname) if (-f $fname); $fname = $KeepDir . "/" . &GetPage($page) . ".kp"; unlink($fname) if (-f $fname); unlink($IndexFile) if ($UseIndex); &EditRecentChanges(1, $page, "") if ($doRC); # Delete page@@ -3887,7 +3984,7 @@
my ($fname, $status, $data, @kplist, %tempSection, $changed); my ($sectName, $newText); $fname = $KeepDir . "/" . &GetPage($page) . ".kp"; return if (!(-f $fname)); ($status, $data) = &ReadFile($fname); return if (!$status);@@ -4019,8 +4116,8 @@
&CreatePageDir($PageDir, $new); # It might not exist yet rename($oldfname, $newfname); &CreatePageDir($KeepDir, $new); $oldkeep = $KeepDir . "/" . &GetPage($old) . ".kp"; $newkeep = $KeepDir . "/" . &GetPage($new) . ".kp"; unlink($newkeep) if (-f $newkeep); # Clean up if needed. rename($oldkeep, $newkeep); unlink($IndexFile) if ($UseIndex);