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);