*不等号"<>"とは? [#Anchor] となっている場合、[[#"不等号""<>""とは?"]]がこの行自身へのリンクに
[[Alias>BracketName#Anchor>Alias]]
1.4.6rcからの差分
--- /lib/file.php Sun Jul 03 23:16:24 2005 +++ /lib/file.php Tue Aug 16 19:36:04 2005 @@ -30,11 +30,14 @@ // Put a data(wiki text) into a physical file(diff, backup, text) function page_write($page, $postdata, $notimestamp = FALSE) { - global $trackback; + global $trackback, $fixed_heading_anchor; if (PKWK_READONLY) return; // Do nothing - $postdata = make_str_rules($postdata); + if($fixed_heading_anchor) + $postdata = make_str_rules($postdata, $page); + else + $postdata = make_str_rules($postdata); // Create and write diff $oldpostdata = is_page($page) ? join('', get_source($page)) : ''; @@ -58,11 +61,291 @@ links_update($page); } +function generate_anchor_id($page, $title, $query_id) +{ + $filename = ANCHOR_DIR . 'global.anchor.ids'; + $ids = array(); + $fp = fopen($filename, 'a+b') + or die_message('cannot open ' . htmlspecialchars($filename)); + flock($fp, LOCK_EX); + $ids = file($filename); + if (!preg_match('/^(?!xml)[A-Z][\w-]*$/i', $query_id) || + in_array(strtolower($query_id)."\n", $ids)){ + do{ + $top = mt_rand(ord('a'), ord('z')); + $md5_sub = substr(md5($page . $title . uniqid($top)), mt_rand(0, 24), 7); + $query_id = chr($top) . $md5_sub; + }while(in_array($query_id."\n", $ids)); + } + fseek($fp, 0, SEEK_END); + fputs($fp, strtolower($query_id)."\n"); + fflush($fp); // 普通fcloseでまとめて + flock($fp, LOCK_UN); // やってくれると思うが + fclose($fp); + return $query_id; +} + +function remove_anchor_ids($lists) +{ + $lists = preg_replace('/$/', "\n", $lists); + $lists[] = ''; + $filename = ANCHOR_DIR . 'global.anchor.ids'; + $ids = array(); + $fp = fopen($filename, 'a+b') + or die_message('cannot open ' . htmlspecialchars($filename)); + flock($fp, LOCK_EX); + $ids = array_diff(file($filename), $lists); + ftruncate($fp, 0); + if(!empty($ids)) + fputs($fp, implode('', $ids)); + fflush($fp); + flock($fp, LOCK_UN); + fclose($fp); +} + +class anchors +{ + var $page; + var $filename; + + var $ids = array(); + var $line_no = array(); + var $heading = array(); + var $titles = array(); + var $others = array(); + + function anchors($page){ + $this->page = $page; + $this->filename = ANCHOR_DIR . encode($page) . '.ids'; + } + + function _unset($key){ + unset($this->ids[$key]); + unset($this->line_no[$key]); + unset($this->heading[$key]); + unset($this->titles[$key]); + unset($this->others[$key]); + } +} + +class read_anchors extends anchors +{ + function read_anchors($page){ + parent::anchors($page); + + if(($fp = @fopen($this->filename, 'rb')) === FALSE) + return; + flock($fp, LOCK_SH); + $data = fread($fp, filesize($this->filename)); + flock($fp, LOCK_UN); + fclose($fp); + $mathces = array(); + if(preg_match_all('/^([^\t\n]*)\t(\d+)\t(\d)\t([^\t\n]*)\t(.*)$/m', $data, $matches) + != substr_count($data, "\n")){ + error_log('PukiWiki Warning: broken anchor file detected -> ' . + htmlspecialchars($page), 0); + }else{ + unset($data, $matches[0]); + $this->ids = $matches[1]; + $this->line_no = $matches[2]; + $this->heading = $matches[3]; + $this->titles = str_replace('>', "\t", $matches[4]); + $this->others = $matches[5]; + } + } + + function get_data(){ + return array( + 'ids' => $this->ids, + 'line_no' => $this->line_no, + 'heading' => $this->heading, + 'titles' => $this->titles, + 'others' => $this->others + ); + } +} + +class write_anchors extends anchors +{ + var $current_line; + var $count = 0; + var $lines = array(); + + function write_anchors($page){ + require_once(PLUGIN_DIR . 'aname.inc.php'); + function_exists('plugin_aname_getargs') + or die_message('plugin <em>aname.inc.php</em> has not been updated.'); + parent::anchors($page); + } + + function write(){ + if(!empty($this->lines)){ + ksort($this->lines); + $fp = fopen($this->filename, 'ab') + or die_message('cannot write ' . htmlspecialchars($this->filename)); + flock($fp, LOCK_EX); + ftruncate($fp, 0); + fputs($fp, implode('', $this->lines)); + fflush($fp); + flock($fp, LOCK_UN); + fclose($fp); + }else if(file_exists($this->filename)) + @unlink($this->filename); + } + + function set($key){ + $this->lines[$key] = + $this->ids[$key] . "\t" . + $this->line_no[$key] . "\t" . + $this->heading[$key] . "\t" . + str_replace("\t", '>', $this->titles[$key]) . "\t" . + $this->others[$key] . "\n"; + $this->_unset($key); + } + + function splice_aname_id_callback($matches){ + if(!preg_match('/^[&#]aname$/i', $matches[1])){ + if(!isset($matches[3])) + return $matches[0]; + $body = preg_replace_callback( + '/(&\w+)(?:\(((?:(?!\)[;{]).)*)\))?(?:\{((?:(?R)|(?!};).)*)\})?;/', + array(&$this, 'splice_aname_id_callback'), + $matches[3] + ); + return substr_replace($matches[0], $body, -(strlen($matches[3])+2), -2); + } + $args = csv_explode(',', $matches[2]); + if(empty($args)) + return $matches[0]; + + $_args = plugin_aname_getargs( + $matches[1]{0} == '#' ? $args : array_merge($args, @(array)$matches[3]), FALSE); + if(!is_array($_args) || $_args['noid']) + return $matches[0]; + + $this->ids[$this->count] = $_args['id'] != '*' ? $_args['id'] : ''; + $this->line_no[$this->count] = $this->current_line; + $this->heading[$this->count] = '0'; // ここ悩む… + $this->titles[$this->count] = trim(strip_decoration($_args['body'])); + $this->others[$this->count] = ''; + + return substr_replace($matches[0], "\x08".$this->count++."\x08", 7, strlen($args[0])); + } + + function splice_anchor(&$line, $i){ + $this->current_line = $i; + $matches = array(); + + if(preg_match('/^(#aname)\((.*)\)(.*)$/i', $line, $matches)){ + $line = $this->splice_aname_id_callback($matches); + return; + } + if(preg_match('/^(\*{1,3}(.*?))(?:\[#([A-Za-z][\w-]*)\](.*))?$/', $line, $matches)){ + $this->ids[$this->count] = isset($matches[3]) ? $matches[3] : ''; + $this->line_no[$this->count] = $this->current_line; + $this->heading[$this->count] = (string)min(strspn($matches[1], '*'), 3); + $this->titles[$this->count] = trim(strip_decoration($matches[2])); + $this->others[$this->count] = isset($matches[4]) ? trim(strip_decoration($matches[4])) : ''; + + $line = $matches[1] . (isset($matches[3]) ? '' : ' ') . + "[#\x08" . $this->count++ . "\x08]" . @$matches[4]; + } + $line = preg_replace_callback( + '/(&\w+)(?:\(((?:(?!\)[;{]).)*)\))?(?:\{((?:(?R)|(?!};).)*)\})?;/', + array(&$this, 'splice_aname_id_callback'), + $line + ); + } + + function reassign_id(&$source){ + $org = &new read_anchors($this->page); + + foreach($org->ids as $org_key=>$id){ + if(is_int($new_key = array_search($id, $this->ids)) && + $this->heading[$new_key] == $org->heading[$org_key] && + $this->titles[$new_key] == $org->titles[$org_key]){ + $this->set($new_key); + $org->_unset($org_key); + } + } + // id一致・title不一致 + foreach($org->ids as $org_key=>$id){ + if(is_int($new_key = array_search($id, $this->ids))){ + $this->set($new_key); + $org->_unset($org_key); + } + } + // title一致・id不一致 + foreach($org->titles as $org_key=>$title){ + if(is_int($new_key = array_search($title, $this->titles))){ + $this->ids[$new_key] = $org->ids[$org_key]; + $this->set($new_key); + $org->_unset($org_key); + } + } + if(!empty($org->ids)) + remove_anchor_ids($org->ids); + unset($org); + + foreach($this->ids as $key=>$id){ + $assigned_id = generate_anchor_id($this->page, $this->titles[$key], $id); + if($assigned_id != $id) + $this->ids[$key] = $assigned_id; + $this->set($key); + } + + $source = preg_replace( + '/\x08(\d+)\x08/e', + 'substr($this->lines[$1], 0, strpos($this->lines[$1], "\t"))', + $source + ); + $this->write(); + } +} + +function get_anchors($page = '', $nocache = FALSE) +{ + global $vars; + static $cache; + + if($page == '') + $page = $vars['page']; + if(!$nocache) + $retval = &$cache[$page]; + if(!isset($retval)){ + $anchors = &new read_anchors($page); + $retval = $anchors->get_data(); + } + return $retval; +} + +function search_anchors(&$search, $page, $heading_only = FALSE) +{ + $is_id = preg_match('/^#?[A-Za-z][\w-]*$/', $search); + $search = preg_replace('/(?:^#?"?|"(")|"$)/', '$1', $search); + $s_search = htmlspecialchars(trim($search)); + if($s_search == '') + return ''; + $anchors = get_anchors($page); + if($is_id){ + $key = array_search($s_search, $anchors['ids']); + if(is_int($key) && (!$heading_only || $anchors['heading'][$key])) + return $s_search; + }else{ + $key = array_search($s_search, $anchors['titles']); + if(is_int($key) && (!$heading_only || $anchors['heading'][$key])) + return $anchors['ids'][$key]; + } + return (!$heading_only && $is_id) ? $s_search : ''; +} + // Modify ogirinal text with user-defined / system-defined rules -function make_str_rules($source) +function make_str_rules($source, $page = NULL) { - global $str_rules, $fixed_heading_anchor; + global $str_rules; + if(isset($page)) + $anchors = &new write_anchors($page); $lines = explode("\n", $source); $count = count($lines); @@ -98,25 +381,19 @@ // Replace with $str_rules foreach ($str_rules as $pattern => $replacement) $line = preg_replace('/' . $pattern . '/', $replacement, $line); - - // Adding fixed anchor into headings - if ($fixed_heading_anchor && - preg_match('/^(\*{1,3}(.(?!\[#[A-Za-z][\w-]+\]))+)$/', $line, $matches)) - { - // Generate ID: - // A random alphabetic letter + 7 letters of random strings from md() - $anchor = chr(mt_rand(ord('a'), ord('z'))) . - substr(md5(uniqid(substr($matches[1], 0, 100), 1)), mt_rand(0, 24), 7); - $line = rtrim($matches[1]) . ' [#' . $anchor . ']'; - } - } + if(isset($anchors) && substr($line, 0, 2) != '//') + $anchors->splice_anchor($line, $i); + } // Multiline part has no stopper if (! PKWKEXP_DISABLE_MULTILINE_PLUGIN_HACK && $modify === FALSE && $multiline != 0) $lines[] = str_repeat('}', $multiline); - return implode("\n", $lines); + $source = implode("\n", $lines); + if(isset($anchors)) + $anchors->reassign_id($source); + return $source; } // Output to a file
--- /lib/html.php Sun Jul 03 23:51:18 2005 +++ /lib/html.php Mon Aug 15 20:16:56 2005 @@ -329,7 +329,7 @@ // Remove AutoLink marker with AutLink itself function strip_autolink($str) { - return preg_replace('#<!--autolink--><a [^>]+>|</a><!--/autolink-->#', '', $str); + return preg_replace('#(?:<!--autolink-->)?<a [^>]+>|</a>(?:<!--/autolink-->)?#', '', $str); } // Make a backlink. searching-link of the page name, by the page name, for the page name @@ -347,8 +347,6 @@ // Make heading string (remove heading-related decorations from Wiki text) function make_heading(& $str, $strip = TRUE) { - global $NotePattern; - // Cut fixed-heading anchors $id = ''; $matches = array(); @@ -361,32 +359,31 @@ // Cut footnotes and tags if ($strip === TRUE) - $str = strip_htmltag(make_link(preg_replace($NotePattern, '', $str))); + $str = strip_decoration($str); return $id; } +// remove decorations from Wiki text +function strip_decoration($str) +{ + global $NotePattern; + + return strip_htmltag(make_link(preg_replace($NotePattern, '', $str))); +} + // Separate a page-name(or URL or null string) and an anchor // (last one standing) without sharp function anchor_explode($page, $strict_editable = FALSE) { - $pos = strrpos($page, '#'); - if ($pos === FALSE) return array($page, '', FALSE); - - // Ignore the last sharp letter - if ($pos + 1 == strlen($page)) { - $pos = strpos(substr($page, $pos + 1), '#'); - if ($pos === FALSE) return array($page, '', FALSE); - } + $pos = strpos($page, '#'); + if ($pos === FALSE || strlen($page) == $pos + 1) + return array($page, '', FALSE); $s_page = substr($page, 0, $pos); - $anchor = substr($page, $pos + 1); - - if($strict_editable === TRUE && preg_match('/^[a-z][a-f0-9]{7}$/', $anchor)) { - return array ($s_page, $anchor, TRUE); // Seems fixed-anchor - } else { - return array ($s_page, $anchor, FALSE); - } + $anchor = substr($page, $pos); + $id = search_anchors($anchor, $s_page, $strict_editable); + return array($s_page, $id, $strict_editable && $id != ''); } // Check HTTP header()s were sent already, or
--- /lib/make_link.php Mon Jun 27 23:18:08 2005 +++ /lib/make_link.php Mon Aug 15 19:58:36 2005 @@ -551,7 +551,11 @@ | (?:$BracketName) )? -(\#(?:[a-zA-Z][\w-]*)?)? # (4) Anchor +(\#(?: # (4) Anchor + [A-Za-z][\w-]* # ID + | + (?:"[^"]*")+ # Alias for ID +)?)? (?($s2)\]\]) # Close bracket if (2) \]\] # Close bracket EOD; @@ -569,12 +573,14 @@ list(, $alias, , $name, $this->anchor) = $this->splice($arr); if ($name == '' && $this->anchor == '') return FALSE; - if ($name == '' || ! preg_match('/^' . $WikiName . '$/', $name)) { - if ($alias == '') $alias = $name . $this->anchor; - if ($name != '') { - $name = get_fullname($name, $page); - if (! is_pagename($name)) return FALSE; - } + if ($name != '' && ! preg_match('/^' . $WikiName . '$/', $name)) { + $name = get_fullname($name, $page); + if (! is_pagename($name)) return FALSE; + } + if ($this->anchor != ''){ + $id = search_anchors($this->anchor, $name); + if ($alias == '') $alias = $name . '#' . $this->anchor; + $this->anchor = '#' . $id; } return parent::setParam($page, $name, '', 'pagename', $alias);
--- /plugin/aname.inc.php Fri Jun 17 00:04:08 2005 +++ /plugin/aname.inc.php Sat Aug 06 03:30:02 2005 @@ -27,6 +27,8 @@ // #aname function plugin_aname_convert() { + if(func_num_args() < 1) + return plugin_aname_usage(TRUE); $args = func_get_args(); // Zero or more return plugin_aname_tag($args); } @@ -34,6 +36,8 @@ // &aname; function plugin_aname_inline() { + if(func_num_args() < 2) + return plugin_aname_usage(FALSE); $args = func_get_args(); // ONE or more $body = strip_autolink(array_pop($args)); @@ -49,7 +53,7 @@ if ($convert) { return '#aname(anchorID[[,super][,full][,noid],Link title])'; } else { - return '&aname(anchorID[,super][,full][,noid]){[Link title]}'; + return '&aname(anchorID[,super][,full][,noid]){[Link title]};'; } } else { if ($convert) { @@ -60,42 +64,52 @@ } } -// Aname plugin itself -function plugin_aname_tag($args = array(), $convert = TRUE) +function plugin_aname_getargs($args = array(), $check_dup_id) { - global $vars; static $_id = array(); - if (empty($args) || $args[0] == '') return plugin_aname_usage($convert); + if ($args[0] == '') return ''; $id = array_shift($args); $body = ''; if (! empty($args)) $body = array_pop($args); - $f_noid = in_array('noid', $args); // Option: Without id attribute - $f_super = in_array('super', $args); // Option: CSS class - $f_full = in_array('full', $args); // Option: With full(absolute) URI + $noid = in_array('noid', $args); // Option: Without id attribute + $super = in_array('super', $args); // Option: CSS class + $full = in_array('full', $args); // Option: With full(absolute) URI + $hide = in_array('hide', $args); if ($body == '') { - if ($f_noid) return plugin_aname_usage($convert, 'Meaningless(No link-title with \'noid\')'); - if ($f_super) return plugin_aname_usage($convert, 'Meaningless(No link-title with \'super\')'); - if ($f_full) return plugin_aname_usage($convert, 'Meaningless(No link-title with \'full\')'); + if ($noid) return 'Meaningless(No link-title with \'noid\')'; + if ($super) return 'Meaningless(No link-title with \'super\')'; + if ($full) return 'Meaningless(No link-title with \'full\')'; } - if (PLUGIN_ANAME_ID_MUST_UNIQUE && isset($_id[$id]) && ! $f_noid) { - return plugin_aname_usage($convert, 'ID already used: '. $id); + if ($check_dup_id && isset($_id[$id]) && ! $noid) { + return 'ID already used: '. $id; } else { if (strlen($id) > PLUGIN_ANAME_ID_MAX) - return plugin_aname_usage($convert, 'ID too long'); - if (! preg_match(PLUGIN_ANAME_ID_REGEX, $id)) - return plugin_aname_usage($convert, 'Invalid ID string: ' . - htmlspecialchars($id)); - $_id[$id] = TRUE; // Set + return 'ID too long'; + if ($id != '*' && ! preg_match(PLUGIN_ANAME_ID_REGEX, $id)) + return 'Invalid ID string: ' . htmlspecialchars($id); + if ($check_dup_id) $_id[$id] = TRUE; // Set } - if ($convert) $body = htmlspecialchars($body); - $id = htmlspecialchars($id); // Insurance - $class = $f_super ? 'anchor_super' : 'anchor'; - $attr_id = $f_noid ? '' : ' id="' . $id . '"'; - $url = $f_full ? get_script_uri() . '?' . rawurlencode($vars['page']) : ''; + return compact('id', 'super', 'full', 'noid', 'hide', 'body'); +} + +// Aname plugin itself +function plugin_aname_tag($args = array(), $convert = TRUE) +{ + global $vars; + + $args = plugin_aname_getargs($args, PLUGIN_ANAME_ID_MUST_UNIQUE); + if (! is_array($args)) + return plugin_aname_usage($convert, $args); + + $body = $args['hide'] ? '' : ($convert ? htmlspecialchars($args['body']) : $args['body']); + $id = htmlspecialchars($args['id']); // Insurance + $class = $args['super'] ? 'anchor_super' : 'anchor'; + $attr_id = $args['noid'] ? '' : ' id="' . $id . '"'; + $url = $args['full'] ? get_script_uri() . '?' . rawurlencode($vars['page']) : ''; if ($body != '') { $href = ' href="' . $url . '#' . $id . '"'; $title = ' title="' . $id . '"';
--- /plugin/rename.inc.php Sun Feb 27 16:57:26 2005 +++ /plugin/rename.inc.php Fri Aug 05 23:31:18 2005 @@ -297,7 +297,7 @@ function plugin_rename_get_files($pages) { $files = array(); - $dirs = array(BACKUP_DIR, DIFF_DIR, DATA_DIR); + $dirs = array(BACKUP_DIR, DIFF_DIR, DATA_DIR, ANCHOR_DIR); if (exist_plugin_convert('attach')) $dirs[] = UPLOAD_DIR; if (exist_plugin_convert('counter')) $dirs[] = COUNTER_DIR; // and more ...
--- /pukiwiki.ini.php Sun Jul 03 23:16:24 2005 +++ /pukiwiki.ini.php Sat Aug 06 16:31:50 2005 @@ -78,6 +78,7 @@ define('COUNTER_DIR', DATA_HOME . 'counter/' ); // Counter plugin's counts define('TRACKBACK_DIR', DATA_HOME . 'trackback/'); // TrackBack logs define('PLUGIN_DIR', DATA_HOME . 'plugin/' ); // Plugin directory +define('ANCHOR_DIR', DATA_HOME . 'anchor/' ); // Fixed anchor data ///////////////////////////////////////////////// // Directory settings II (ended with '/')
<?php // PukiWiki - Yet another WikiWikiWeb clone // anchors.inc.php, v0.02 // // Update anchor cache plugin // Message setting function plugin_anchors_init() { $messages = array( '_anchors_messages'=>array( 'title_update' => 'アンカーキャッシュ更新', 'msg_adminpass' => '管理者パスワード', 'msg_update' => '重複IDの書き換え結果は画面表示のみ', 'msg_diff' => '書き換えたページの差分にも残す', 'btn_submit' => '実行', 'msg_done' => 'キャッシュの更新が完了しました。', 'msg_no_dup_id' => '重複IDはありませんでした。', 'msg_usage' => ' * 処理内容 :キャッシュを更新| 凍結ページを含む全てのページをスキャンし、重複アンカーIDの調査・再割り当てを行ない、 アンカーのエイリアス名をキャッシュします。 * 注意 実行には数分かかる場合もあります。実行ボタンを押したあと、しばらくお待ちください。 * 実行 管理者パスワードを入力して、[実行]ボタンをクリックしてください。 ' ) ); set_plugin_messages($messages); } function plugin_anchors_action() { global $script, $post, $vars, $fixed_heading_anchor; global $_anchors_messages; $msg = & $_anchors_messages['title_update']; $body = ''; if (PKWK_READONLY) die_message('PKWK_READONLY prohibits this'); if (!$fixed_heading_anchor) die_message('$fixed_heading_anchor disabled'); if (empty($vars['action']) || empty($post['adminpass']) || ! pkwk_login($post['adminpass'])) { $body = convert_html($_anchors_messages['msg_usage']); $body .= <<<EOD <form method="POST" action="$script"> <div> <input type="hidden" name="plugin" value="anchors" /> <input type="radio" name="action" value="update" id="_p_anchors_update" checked="checked" /> <label for="_p_anchors_update">{$_anchors_messages['msg_update']}</label><br /> <input type="radio" name="action" value="diff" id="_p_anchors_diff" /> <label for="_p_anchors_diff">{$_anchors_messages['msg_diff']}</label><br /> <label for="_p_anchors_adminpass">{$_anchors_messages['msg_adminpass']}</label> <input type="password" name="adminpass" id="_p_anchors_adminpass" size="20" value="" /> <input type="submit" value="{$_anchors_messages['btn_submit']}" /> </div> </form> EOD; }else if(preg_match('/^(?:update|diff)$/', $vars['action'])) { $retval = plugin_anchors_update($vars['action']); if($retval == '') $retval = $_anchors_messages['msg_no_dup_id']; $body = $_anchors_messages['msg_done'] . "<br />\n" . $retval; }else{ $body = $_anchors_messages['err_invalid' ]; } return compact('msg', 'body'); } function plugin_anchors_update($action) { $fp = @fopen(ANCHOR_DIR . 'global.anchor.ids', 'w') or die_message('cannot open ' . htmlspecialchars(ANCHOR_DIR . 'global.anchor.ids')); fclose($fp); foreach(get_existpages(ANCHOR_DIR, '.ids') as $page) @unlink(ANCHOR_DIR . encode($page) . '.ids'); $pages = array(); foreach(get_existpages() as $page) $pages[$page] = get_filetime($page); asort($pages); $retval = ''; foreach(array_keys($pages) as $page){ $old_data = implode('', get_source($page)); $new_data = make_str_rules($old_data, $page); $diff_data = do_diff($old_data, $new_data); $diffs = preg_grep('/^[+-]/', explode("\n", htmlspecialchars($diff_data))); if(!empty($diffs)){ file_write(DATA_DIR, $page, $new_data, TRUE); if($action == 'diff') file_write(DIFF_DIR, $page, $diff_data); $diffs = preg_replace('/^-(.*)$/', '<span class="diff_removed"> $1</span>', $diffs); $diffs = preg_replace('/^\+(.*)$/', '<span class="diff_added" > $1</span>', $diffs); $retval .= '<p>' . htmlspecialchars($page) . "</p>\n"; $retval .= "<pre>\n" . implode("\n", $diffs) . "\n</pre>\n"; } } return $retval; } ?>