/[suikacvs]/webroot/swe/lib/suikawiki/main.pl
Suika

Contents of /webroot/swe/lib/suikawiki/main.pl

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.39 - (hide annotations) (download)
Mon Sep 14 02:41:01 2009 UTC (15 years, 7 months ago) by wakaba
Branch: MAIN
Changes since 1.38: +2 -1 lines
File MIME type: text/plain
++ swe/lib/SWE/ChangeLog	14 Sep 2009 02:38:21 -0000
	* Lang.pm: Changed module names.

2009-09-14  Wakaba  <wakaba@suika.fam.cx>

++ swe/lib/SWE/Object/Document/ChangeLog	14 Sep 2009 02:40:50 -0000
	* SWML.pm: New module.

2009-09-14  Wakaba  <wakaba@suika.fam.cx>

++ swe/lib/SWE/Object/ChangeLog	14 Sep 2009 02:40:09 -0000
	* Document.pm: Added |rebless| method family.  Moved
	SWML-to-HTML/XML/text convertion methods to another module.

	* Document/: New directory.

2009-09-14  Wakaba  <wakaba@suika.fam.cx>

++ swe/lib/suikawiki/ChangeLog	14 Sep 2009 02:39:42 -0000
	* main.pl: Call the |rebless| method on the SWE::Object::Document
	module such that format-dependent methods can be invoked.

2009-09-14  Wakaba  <wakaba@suika.fam.cx>

1 wakaba 1.1 use strict;
2 wakaba 1.36 use warnings;
3 wakaba 1.1 use feature 'state';
4    
5     ## --- Configurations
6    
7     our $db_dir_name;
8     my $db_sw3_dir_name = $db_dir_name . 'sw3pages/';
9     my $db_global_lock_dir_name = $db_dir_name;
10     my $db_id_dir_name = $db_dir_name . q[ids/];
11     my $db_name_dir_name = $db_dir_name . q[names/];
12    
13     ## --- Common modules
14    
15     require Encode;
16    
17     require Message::DOM::DOMImplementation;
18     my $dom = Message::DOM::DOMImplementation->new;
19    
20     use Message::CGI::Util qw/percent_encode percent_encode_na
21 wakaba 1.3 percent_decode htescape get_absolute_url
22     datetime_in_content/;
23 wakaba 1.1
24     require Char::Normalize::FullwidthHalfwidth;
25    
26 wakaba 1.38 use SWE::Lang qw/@ContentMediaType/;
27    
28 wakaba 1.1 ## --- Prepares database access variables (commonly used ones)
29    
30 wakaba 1.7 require SWE::DB;
31     my $db = SWE::DB->new;
32 wakaba 1.9 $db->db_dir_name = $db_dir_name;
33 wakaba 1.7 $db->global_lock_dir_name = $db_global_lock_dir_name;
34     $db->id_dir_name = $db_id_dir_name;
35     $db->name_dir_name = $db_name_dir_name;
36 wakaba 1.9 $db->sw3db_dir_name = $db_sw3_dir_name;
37 wakaba 1.7
38 wakaba 1.1 require SWE::DB::SuikaWiki3PageList2;
39     my $sw3_pages = SWE::DB::SuikaWiki3PageList2->new;
40     $sw3_pages->{root_directory_name} = $db_sw3_dir_name;
41    
42     require SWE::DB::Lock;
43     my $names_lock = SWE::DB::Lock->new;
44     $names_lock->{file_name} = $db_global_lock_dir_name . 'ids.lock';
45 wakaba 1.17 $names_lock->lock_type ('Names');
46 wakaba 1.1 ## NOTE: This lock MUST be used when $sw3pages or $name_prop_db is updated.
47    
48     require SWE::DB::IDProps;
49     my $id_prop_db = SWE::DB::IDProps->new;
50     $id_prop_db->{root_directory_name} = $db_id_dir_name;
51     $id_prop_db->{leaf_suffix} = '.props';
52    
53     require SWE::DB::IDLocks;
54     my $id_locks = SWE::DB::IDLocks->new;
55     $id_locks->{root_directory_name} = $db_id_dir_name;
56     $id_locks->{leaf_suffix} = '.lock';
57    
58     require SWE::DB::HashedProps;
59     my $name_prop_db = SWE::DB::HashedProps->new;
60     $name_prop_db->{root_directory_name} = $db_name_dir_name;
61     $name_prop_db->{leaf_suffix} = '.props';
62    
63     my $cache_prop_db = SWE::DB::IDProps->new;
64     $cache_prop_db->{root_directory_name} = $db_id_dir_name;
65     $cache_prop_db->{leaf_suffix} = '.cacheprops';
66    
67     require SWE::DB::IDText;
68     my $content_db = SWE::DB::IDText->new;
69     $content_db->{root_directory_name} = $db_id_dir_name;
70     $content_db->{leaf_suffix} = '.txt';
71    
72     ## --- Process Request-URI
73    
74     require Message::CGI::HTTP;
75     my $cgi = Message::CGI::HTTP->new;
76     $cgi->{decoder}->{'#default'} = sub {
77     return Encode::decode ('utf-8', $_[1]);
78     };
79    
80     ## NOTE: This script requires the server set REQUEST_URI and
81     ## SCRIPT_NAME which is the part of the REQUEST_URI that identifies
82     ## the script.
83    
84     my $rurl = $dom->create_uri_reference ($cgi->request_uri)
85     ->get_uri_reference;
86     my $sname = $dom->create_uri_reference
87     (percent_encode_na ($cgi->get_meta_variable ('SCRIPT_NAME')))
88     ->get_absolute_reference ($rurl)
89     ->get_uri_reference;
90     my $path = $rurl->get_relative_reference ($sname);
91     $path->uri_query (undef);
92     $path->uri_fragment (undef);
93    
94     my $param;
95     if ($path =~ s[;([^/]*)\z][]) {
96     $param = percent_decode ($1);
97     }
98     my $dollar;
99     if ($path =~ s[\$([^/]*)\z][]) {
100     $dollar = percent_decode ($1);
101     }
102    
103     my @path = map { s/\+/%2F/g; percent_decode ($_) } split m#/#, $path, -1;
104     shift @path; # script's name
105    
106     ## --- Process request and generate response
107    
108     sub HTML_NS () { q<http://www.w3.org/1999/xhtml> }
109    
110     if ($path[0] eq 'n' and @path == 2) {
111     my $name = normalize_name ($path[1]);
112    
113     unless (length $name) {
114     our $homepage_name;
115     http_redirect (303, 'See other', get_page_url ($homepage_name, undef));
116     }
117    
118     unless (defined $param) {
119 wakaba 1.23 my ($id, $ids) = prepare_by_name ($name, $dollar);
120 wakaba 1.1
121 wakaba 1.23 if (defined $dollar and not defined $id) {
122     http_redirect (301, 'Not found', get_page_url ($name, undef));
123 wakaba 1.1 }
124    
125     my $format = $cgi->get_parameter ('format') // 'html';
126    
127     ## TODO: Is it semantically valid that there is path?format=html
128     ## (200) but no path?format=xml (404)?
129    
130 wakaba 1.36 require SWE::Object::Document;
131     my $docobj = SWE::Object::Document->new (id => $id, db => $db);
132 wakaba 1.39 $docobj->rebless;
133 wakaba 1.36
134 wakaba 1.1 if ($format eq 'text' and defined $id) {
135 wakaba 1.36 $docobj->{content_db} = $content_db; ## XXX
136     $docobj->{id_prop_db} = $id_prop_db; ## XXX
137 wakaba 1.1
138     binmode STDOUT, ':encoding(utf-8)';
139 wakaba 1.36 print qq[Content-Type: @{[$docobj->to_text_media_type]}; charset=utf-8\n\n];
140     print ${$docobj->to_text};
141 wakaba 1.1 exit;
142     } elsif ($format eq 'xml' and defined $id) {
143 wakaba 1.36 ## XXX
144     $docobj->{id_locks} = $id_locks;
145     $docobj->{id_prop_db} = $id_prop_db;
146     $docobj->{cache_prop_db} = $cache_prop_db;
147     $docobj->{swml_to_xml} = \&get_xml_data;
148    
149     my $xmldoc = $docobj->to_xml (styled => scalar $cgi->get_parameter ('styled'));
150     if ($xmldoc) {
151     binmode STDOUT, ':encoding(utf-8)';
152     print qq[Content-Type: @{[$docobj->to_xml_media_type]}; charset=utf-8\n\n];
153    
154     print $xmldoc->inner_html;
155     } else {
156     http_error (406, q{format=xml is not supported for this page});
157 wakaba 1.1 }
158     exit;
159     } elsif ($format eq 'html') {
160     my $html_doc;
161     my $html_container;
162     my $title_text;
163 wakaba 1.3 my $id_prop;
164 wakaba 1.1 if (defined $id) {
165 wakaba 1.37 # XXX
166     $docobj->{id_locks} = $id_locks;
167     $docobj->{id_prop_db} = $id_prop_db;
168     $docobj->{cache_prop_db} = $cache_prop_db;
169     $docobj->{swml_to_xml} = \&get_xml_data;
170     $docobj->{name} = $name;
171     $docobj->{get_page_url} = \&get_page_url;
172    
173 wakaba 1.36 $docobj->lock;
174 wakaba 1.37 ($html_doc, $html_container) = $docobj->to_html_fragment;
175 wakaba 1.1
176     $title_text = $id_prop->{title};
177     ## TODO: $title_type
178    
179 wakaba 1.36 $docobj->unlock;
180 wakaba 1.37 }
181    
182     unless ($html_doc) {
183 wakaba 1.1 $html_doc = $dom->create_document;
184     $html_doc->strict_error_checking (0);
185     $html_doc->dom_config->set_parameter
186     ('http://suika.fam.cx/www/2006/dom-config/strict-document-children' => 0);
187     $html_doc->manakai_is_html (1);
188     }
189    
190     $html_doc->inner_html ('<!DOCTYPE HTML><title></title>');
191    
192     $html_doc->get_elements_by_tag_name ('title')->[0]->text_content ($name);
193 wakaba 1.2 my @link = ({rel => 'alternate',
194 wakaba 1.37 type => $docobj->to_text_media_type,
195 wakaba 1.2 href => get_page_url ($name, undef, $id) . '?format=text'},
196     {rel => 'alternate',
197 wakaba 1.37 type => $docobj->to_xml_media_type,
198 wakaba 1.2 href => get_page_url ($name, undef, $id) . '?format=xml'},
199     {rel => 'archives',
200     href => get_page_url ($name, undef, undef) . ';history',
201     title => 'History of the page name'});
202     if (defined $id) {
203     push @link, {rel => 'archives',
204     href => '../i/' . $id . ';history',
205     title => 'History of the page content'};
206     }
207     set_head_content ($html_doc, $id, \@link);
208 wakaba 1.1
209     my $body_el = $html_doc->last_child->last_child;
210    
211     my $h1_el = $html_doc->create_element_ns (HTML_NS, 'h1');
212     my $a_el = $html_doc->create_element_ns (HTML_NS, 'a');
213     $a_el->set_attribute (href => get_page_url ($name, undef));
214     $a_el->set_attribute (rel => 'bookmark');
215     $a_el->text_content ($name);
216     $h1_el->append_child ($a_el);
217     $body_el->append_child ($h1_el);
218    
219     if (@$ids) {
220     my $nav_el = $html_doc->create_element_ns (HTML_NS, 'div');
221     $nav_el->set_attribute (class => 'nav swe-ids');
222     $nav_el->manakai_append_text
223     (@$ids == 1 ? 'There is another page with same name:'
224     : 'There are other pages with same name:');
225     my $ul_el = $html_doc->create_element_ns (HTML_NS, 'ul');
226     for my $id (@$ids) {
227     my $li_el = $html_doc->create_element_ns (HTML_NS, 'li');
228     $li_el->inner_html (q[<a></a>]);
229     my $a_el = $li_el->first_child;
230     my $id_prop = $id_prop_db->get_data ($id);
231     $a_el->text_content
232     (length $id_prop->{title} ? $id_prop->{title}
233     : [keys %{$id_prop->{name}}]->[0] // $id); ## TODO: title-type
234     $a_el->set_attribute (href => get_page_url ($name, $name, $id));
235     $ul_el->append_child ($li_el);
236     }
237     $nav_el->append_child ($ul_el);
238     $body_el->append_child ($nav_el);
239     }
240    
241     my $nav_el = $html_doc->create_element_ns (HTML_NS, 'div');
242 wakaba 1.4 $nav_el->set_attribute (class => 'nav tools');
243 wakaba 1.1 $nav_el->inner_html (q[<a rel=edit>Edit</a> <a href="../new-page">New</a>]);
244     if (defined $id) {
245     $nav_el->first_child->set_attribute (href => '../i/' . $id . ';edit');
246     } else {
247     $nav_el->first_child->set_attribute
248     (href => '../new-page?names=' . percent_encode ($name));
249     }
250     $body_el->append_child ($nav_el);
251    
252     if ($html_container) {
253     my $article_el = $html_doc->create_element_ns (HTML_NS, 'div');
254     $article_el->set_attribute (class => 'article');
255    
256     my $h2_el = $html_doc->create_element_ns (HTML_NS, 'h2');
257     $h2_el->text_content (length $title_text ? $title_text : $name);
258     ## TODO: {'title-type'};
259     $article_el->append_child ($h2_el);
260    
261     while (@{$html_container->child_nodes}) {
262     $article_el->append_child ($html_container->first_child);
263     }
264 wakaba 1.3
265     my $modified = $id_prop->{modified};
266     if (defined $modified) {
267     my $footer_el = $html_doc->create_element_ns (HTML_NS, 'div');
268     $footer_el->set_attribute (class => 'footer swe-updated');
269     $footer_el->inner_html ('Updated: <time></time>');
270     $footer_el->last_child->text_content
271     (datetime_in_content ($modified));
272     $article_el->append_child ($footer_el);
273     }
274 wakaba 1.1
275     $body_el->append_child ($article_el);
276     }
277    
278     my $footer_el = $html_doc->create_element_ns (HTML_NS, 'footer');
279     $footer_el->set_attribute (class => 'footer');
280     $footer_el->inner_html (q[<p class=copyright><small>&copy; Authors. See <a rel=license>license terms</a>. There might also be additional terms applied for this page.</small>]);
281     $body_el->append_child ($footer_el);
282    
283     my $a_el = $footer_el->get_elements_by_tag_name ('a')->[0];
284     our $license_name;
285     $a_el->set_attribute (href => get_page_url ($license_name));
286 wakaba 1.18
287     set_foot_content ($html_doc);
288 wakaba 1.1
289     binmode STDOUT, ':encoding(utf-8)';
290     print qq[Content-Type: text/html; charset=utf-8\n\n];
291     print $html_doc->inner_html;
292     exit;
293     }
294    
295     exit;
296 wakaba 1.2 } elsif ($param eq 'history' and not defined $dollar) {
297 wakaba 1.27 my $name_history_db = $db->name_history;
298 wakaba 1.2 my $history = $name_history_db->get_data ($name);
299    
300     binmode STDOUT, ':encoding(utf-8)';
301     print "Content-Type: text/html; charset=utf-8\n\n";
302    
303     my $doc = $dom->create_document;
304     $doc->manakai_is_html (1);
305     $doc->inner_html (q[<!DOCTYPE HTML><html lang=en><title></title><h1></h1>
306     <div class=section><h2>History</h2><table>
307    
308     <thead>
309     <tr><th scope=col>Time<th scope=col>Change
310    
311     <tbody>
312    
313     </table></div>]);
314     set_head_content ($doc, undef, [], []);
315    
316     my $title_el = $doc->get_elements_by_tag_name ('title')->[0];
317     $title_el->inner_html ('History &mdash; ');
318     $title_el->manakai_append_text ($name);
319    
320     my $h1_el = $doc->get_elements_by_tag_name ('h1')->[0];
321     $h1_el->text_content ($name);
322    
323     my $table_el = $doc->get_elements_by_tag_name ('table')->[0];
324     if ($history) {
325     my $tbody_el = $table_el->last_child;
326    
327     for my $entry (@$history) {
328     my $tr_el = $doc->create_element_ns (HTML_NS, 'tr');
329    
330     my $date_cell = $doc->create_element_ns (HTML_NS, 'td');
331     my $date = gmtime ($entry->[0] || 0); ## TODO: ...
332     $date_cell->inner_html ('<time>' . $date . '</time>');
333     $tr_el->append_child ($date_cell);
334    
335     my $change_cell = $doc->create_element_ns (HTML_NS, 'td');
336     if ($entry->[1] eq 'c') {
337     $change_cell->manakai_append_text ('Created');
338     } elsif ($entry->[1] eq 'a') {
339     $change_cell->manakai_append_text ('Associated with ');
340     my $a_el = $doc->create_element_ns (HTML_NS, 'a');
341     $a_el->set_attribute (href => '../i/' . $entry->[2] . ';history');
342     $a_el->text_content ($entry->[2]);
343     $change_cell->append_child ($a_el);
344     } elsif ($entry->[1] eq 'r') {
345     $change_cell->manakai_append_text ('Disassociated from ');
346     my $a_el = $doc->create_element_ns (HTML_NS, 'a');
347     $a_el->set_attribute (href => '../i/' . $entry->[2] . ';history');
348     $a_el->text_content ($entry->[2]);
349     $change_cell->append_child ($a_el);
350     } elsif ($entry->[1] eq 't') {
351     $change_cell->manakai_append_text
352     ('Converted from SuikaWiki3 database');
353     } else {
354     $change_cell->manakai_append_text ($entry->[1]);
355     }
356     $tr_el->append_child ($change_cell);
357    
358     $tbody_el->append_child ($tr_el);
359     }
360     } else {
361     my $p_el = $doc->create_element_ns (HTML_NS, 'p');
362     $p_el->text_content ('No history data.');
363     $table_el->parent_node->replace_child ($p_el, $table_el);
364     }
365    
366 wakaba 1.18 set_foot_content ($doc);
367    
368 wakaba 1.2 print $doc->inner_html;
369     exit;
370 wakaba 1.5 } elsif ($param eq 'search' and not defined $dollar) {
371     my $names = [];
372     for_unique_words ($name => sub {
373     push @$names, shift;
374     });
375    
376 wakaba 1.7 my $names_index_db = $db->name_inverted_index;
377 wakaba 1.5 my $index = {};
378     {
379     my $name = shift @$names;
380     last unless defined $name;
381     $index = $names_index_db->get_data ($name);
382     }
383    
384     ## TOOD: "NOT" operation
385    
386     for my $name (@$names) {
387     my $ids = $names_index_db->get_data ($name);
388     my @index = keys %$index;
389     for my $id (@index) {
390     delete $index->{$id} unless $ids->{$id};
391     }
392     for my $id (keys %$ids) {
393     if (defined $index->{$id}) {
394     $index->{$id} *= $ids->{$id};
395     }
396     }
397     }
398    
399     binmode STDOUT, ':encoding(utf-8)';
400     print "Content-Type: text/plain; charset=utf-8\n\n";
401    
402     for my $id (sort {$index->{$b} <=> $index->{$a}} keys %$index) {
403    
404     my $id_prop = $id_prop_db->get_data ($id);
405     my $name = [keys %{$id_prop->{name}}]->[0] // $id;
406    
407     print $index->{$id}, "\t", $id, "\t", $name, "\n";
408     }
409     exit;
410 wakaba 1.24 } elsif ($param eq 'posturl') {
411     my ($id, undef) = prepare_by_name ($name, $dollar);
412    
413     if (defined $dollar and not defined $id) {
414     http_error (404, 'Not found');
415     }
416    
417 wakaba 1.32 if ($cgi->request_method eq 'POST') {
418 wakaba 1.31 my $user = '(anon)'; #$cgi->remote_user // '(anon)';
419 wakaba 1.35 my $added_text = '<' . ($cgi->get_parameter ('url') // '') . '>';
420     {
421     my $credit = $cgi->get_parameter ('credit') // '';
422     $added_text = '(' . $credit . ")\n" . $added_text if length $credit;
423    
424     my $timestamp = $cgi->get_parameter ('timestamp') // '';
425     if (length $timestamp) {
426     $added_text = '(Referenced: [TIME[' . $timestamp . "]])\n". $added_text;
427     }
428    
429     my $title = $cgi->get_parameter ('title') // '';
430     if (length $title) {
431     $title =~ s/(\[|\])/'''$1'''/g;
432     my $tl = $cgi->get_parameter ('title-lang') // '';
433     if (length $tl) {
434     $title = '[CITE@' . $tl . '[' . $title . ']]';
435     } else {
436     $title = '[CITE[' . $title . ']]';
437     }
438     $added_text = $title . "\n" . $added_text;
439     }
440     }
441 wakaba 1.25 normalize_content (\$added_text);
442    
443 wakaba 1.35 my $anchor = 1;
444 wakaba 1.25 APPEND: { if (defined $id) { ## Existing document
445     ## This must be done before the ID lock.
446     $db->name_inverted_index->lock;
447 wakaba 1.24
448 wakaba 1.25 my $id_lock = $id_locks->get_lock ($id);
449     $id_lock->lock;
450    
451     my $id_prop = $id_prop_db->get_data ($id);
452     last APPEND unless $id_prop;
453     last APPEND if $id_prop->{'content-type'} ne 'text/x-suikawiki';
454    
455     my $textref = $content_db->get_data ($id);
456 wakaba 1.34 my $max = 0;
457     while ($$textref =~ /\[([0-9]+)\]/g) {
458     $max = $1 if $max < $1;
459     }
460     $max++;
461     $$textref .= "\n\n[$max] " . $added_text;
462 wakaba 1.35 $anchor = $max;
463 wakaba 1.25
464     $id_prop->{modified} = time;
465     $id_prop->{hash} = get_hash ($textref);
466    
467 wakaba 1.26 my $vc = $db->vc;
468 wakaba 1.25 local $content_db->{version_control} = $vc;
469     local $id_prop_db->{version_control} = $vc;
470    
471     $content_db->set_data ($id => $textref);
472     $id_prop_db->set_data ($id => $id_prop);
473    
474     $vc->commit_changes ("updated by $user");
475    
476     ## TODO: non-default content-type support
477     my $cache_prop = $cache_prop_db->get_data ($id);
478     my $doc = $id_prop ? get_xml_data ($id, $id_prop, $cache_prop) : undef;
479    
480     if (defined $doc) {
481     update_tfidf ($id, $doc);
482     }
483    
484 wakaba 1.35 if ($cgi->get_parameter ('redirect')) {
485     http_redirect (303, 'Appended', get_page_url ($name, undef, $id) . '#anchor-' . $anchor);
486     } else {
487     print qq[Status: 204 Appended\n\n];
488     }
489 wakaba 1.25 exit;
490     }} # APPEND
491    
492     { ## New document
493 wakaba 1.31 my $new_names = {$name => 1};
494     my $content = '[1] ' . $added_text;
495 wakaba 1.24
496 wakaba 1.31 $names_lock->lock;
497     my $time = time;
498    
499     require SWE::Object::Document;
500     my $document = SWE::Object::Document->new_id (db => $db);
501     $document->{name_prop_db} = $name_prop_db; ## TODO: ...
502     $document->{sw3_pages} = $sw3_pages; ## TODO: ...
503    
504     my $id = $document->id;
505    
506     {
507     ## This must be done before the ID lock.
508     $db->name_inverted_index->lock;
509    
510     my $id_lock = $id_locks->get_lock ($id);
511     $id_lock->lock;
512    
513     my $vc = $db->vc;
514     local $content_db->{version_control} = $vc;
515     local $id_prop_db->{version_control} = $vc;
516     $vc->add_file ($db->id->{file_name});
517    
518     my $id_history_db = $db->id_history;
519     local $id_history_db->{version_control} = $vc;
520    
521     $content_db->set_data ($id => \$content);
522    
523     my $id_props = {};
524    
525     $id_history_db->append_data ($id => [$time, 'c']);
526     $id_props->{modified} = $time;
527    
528     for (keys %$new_names) {
529     $id_props->{name}->{$_} = 1;
530     $id_history_db->append_data ($id => [$time, 'a', $_]);
531     }
532    
533     $id_props->{'content-type'} = 'text/x-suikawiki';
534     $id_props->{hash} = get_hash (\$content);
535     $id_prop_db->set_data ($id => $id_props);
536    
537     $vc->commit_changes ("created by $user");
538    
539     ## TODO: non-default content-type support
540     my $cache_prop = $cache_prop_db->get_data ($id);
541     my $doc = $id_props ? get_xml_data ($id, $id_props, $cache_prop) : undef;
542    
543     if (defined $doc) {
544     $document->update_tfidf ($doc);
545     }
546    
547     $id_lock->unlock;
548     }
549    
550     $document->associate_names ($new_names, user => $user, time => $time);
551    
552 wakaba 1.35 if ($cgi->get_parameter ('redirect')) {
553     http_redirect (303, 'Appended', get_page_url ($name, undef, $id) . '#anchor-' . $anchor);
554     } else {
555     print qq[Status: 204 Appended\n\n];
556     }
557 wakaba 1.31 exit;
558 wakaba 1.24 }
559     } else {
560     http_error (405, 'Method not allowed', 'POST');
561     }
562 wakaba 1.1 } else {
563     $name .= '$' . $dollar if defined $dollar;
564     $name .= ';' . $param;
565     http_redirect (301, 'Not found', get_page_url ($name, undef));
566     }
567     } elsif ($path[0] eq 'i' and @path == 2 and not defined $dollar) {
568     unless (defined $param) {
569     if ($cgi->request_method eq 'POST' or
570     $cgi->request_method eq 'PUT') {
571     my $id = $path[1] + 0;
572    
573 wakaba 1.21 ## This must be done before the ID lock.
574     $db->name_inverted_index->lock;
575    
576 wakaba 1.1 my $id_lock = $id_locks->get_lock ($id);
577     $id_lock->lock;
578    
579     my $id_prop = $id_prop_db->get_data ($id);
580     if ($id_prop) {
581     my $ct = get_content_type_parameter ();
582    
583     my $prev_hash = $cgi->get_parameter ('hash') // '';
584     my $current_hash = $id_prop->{hash} //
585     get_hash ($content_db->get_data ($id) // '');
586     unless ($prev_hash eq $current_hash) {
587     ## TODO: conflict
588     exit;
589     }
590    
591     my $textref = \ ($cgi->get_parameter ('text') // '');
592     normalize_content ($textref);
593    
594     $id_prop->{'content-type'} = $ct;
595     $id_prop->{modified} = time;
596     $id_prop->{hash} = get_hash ($textref);
597    
598     my $title = $cgi->get_parameter ('title') // '';
599     normalize_content (\$title);
600     $id_prop->{title} = $title;
601     $id_prop->{'title-type'} = 'text/plain'; ## TODO: get_parameter
602    
603 wakaba 1.26 my $vc = $db->vc;
604 wakaba 1.1 local $content_db->{version_control} = $vc;
605     local $id_prop_db->{version_control} = $vc;
606    
607     $content_db->set_data ($id => $textref);
608     $id_prop_db->set_data ($id => $id_prop);
609    
610     my $user = '(anon)'; #$cgi->remote_user // '(anon)';
611     $vc->commit_changes ("updated by $user");
612    
613 wakaba 1.21 ## TODO: non-default content-type support
614     my $cache_prop = $cache_prop_db->get_data ($id);
615     my $doc = $id_prop ? get_xml_data ($id, $id_prop, $cache_prop) : undef;
616    
617     if (defined $doc) {
618     update_tfidf ($id, $doc);
619     }
620    
621 wakaba 1.1 my $url = get_page_url ([keys %{$id_prop->{name} or {}}]->[0],
622     undef, 0 + $id);
623     http_redirect (301, 'Saved', $url);
624     #print qq[Status: 204 Saved\n\n];
625     #exit;
626     } else {
627     http_error (404, 'Not found');
628     }
629     } else {
630     http_error (405, 'Method not allowed', 'PUT');
631     }
632     } elsif ($param eq 'edit') {
633     my $id = $path[1] + 0;
634    
635     my $textref = $content_db->get_data ($id);
636     if (defined $textref) {
637     binmode STDOUT, ':encoding(utf-8)';
638     print qq[Content-Type: text/html; charset=utf-8\n\n];
639    
640     ## TODO: <select name=title-type>
641     my $html_doc = $dom->create_document;
642     $html_doc->manakai_is_html (1);
643     $html_doc->inner_html (q[<!DOCTYPE HTML><title>Edit</title>
644     <h1>Edit</h1>
645     <div class="nav swe-names"></div>
646    
647     <div class=section>
648     <h2>Page</h2>
649    
650     <form method=post accept-charset=utf-8>
651     <p><button type=submit>Update</button>
652     <p><label><strong>Page title</strong>:<br>
653     <input name=title></label>
654     <p><label for=page-body-text><strong>Page body</strong></label>:
655     <span class=text-toolbar></span><br>
656     <textarea name=text id=page-body-text></textarea>
657     <p><button type=submit>Update</button>
658     <input type=hidden name=hash>
659     <select name=content-type></select>
660 wakaba 1.3 [<a rel=help>Help</a> / <a rel=license>License</a>]
661 wakaba 1.1 </form>
662    
663     </div>
664    
665     <div class=section>
666     <h2>Page name(s)</h2>
667    
668     <form method=post accept-charset=utf-8>
669     <p><textarea name=names></textarea>
670     <p><button type=submit>Save</button>
671     </form>
672     </div>
673     ]);
674     set_head_content ($html_doc, $id, [],
675     [{name => 'ROBOTS', content => 'NOINDEX'}]);
676     my $form_el = $html_doc->get_elements_by_tag_name ('form')->[0];
677     $form_el->set_attribute (action => $id);
678     my $ta_el = $form_el->get_elements_by_tag_name ('textarea')->[0];
679     $ta_el->text_content ($$textref);
680    
681     my $id_prop = $id_prop_db->get_data ($id);
682    
683     my $title_field = $form_el->get_elements_by_tag_name ('input')->[0];
684     $title_field->set_attribute (value => $id_prop->{title} // '');
685    
686     my $hash = $id_prop->{hash} // get_hash ($textref);
687     my $hash_field = $form_el->get_elements_by_tag_name ('input')->[1];
688     $hash_field->set_attribute (value => $hash);
689    
690     my $ct = $id_prop->{'content-type'} // 'text/x-suikawiki';
691     set_content_type_options
692     ($html_doc,
693     $form_el->get_elements_by_tag_name ('select')->[0] => $ct);
694    
695     my $a_el = $form_el->get_elements_by_tag_name ('a')->[0];
696 wakaba 1.3 our $help_page_name;
697     $a_el->set_attribute (href => get_page_url ($help_page_name));
698    
699     $a_el = $form_el->get_elements_by_tag_name ('a')->[1];
700 wakaba 1.1 our $license_name;
701     $a_el->set_attribute (href => get_page_url ($license_name));
702    
703     $form_el = $html_doc->get_elements_by_tag_name ('form')->[1];
704     $form_el->set_attribute (action => $id . ';names');
705    
706     my $names = $id_prop->{name} || {};
707     $ta_el = $form_el->get_elements_by_tag_name ('textarea')->[0];
708     $ta_el->text_content (join "\x0A", keys %$names);
709    
710     my $nav_el = $html_doc->get_elements_by_tag_name ('div')->[0];
711     for (keys %$names) {
712     my $a_el = $html_doc->create_element_ns (HTML_NS, 'a');
713     $a_el->set_attribute (href => get_page_url ($_, undef, $id));
714     $a_el->text_content ($_);
715     $nav_el->append_child ($a_el);
716     $nav_el->manakai_append_text (' ');
717     }
718 wakaba 1.18
719     set_foot_content ($html_doc);
720 wakaba 1.1
721     print $html_doc->inner_html;
722     exit;
723     }
724     } elsif ($param eq 'names') {
725     if ($cgi->request_method eq 'POST' or
726     $cgi->request_method eq 'PUT') {
727     my $id = $path[1] + 0;
728    
729 wakaba 1.26 my $vc = $db->vc;
730 wakaba 1.1 local $name_prop_db->{version_control} = $vc;
731     local $id_prop_db->{version_control} = $vc;
732    
733 wakaba 1.27 my $id_history_db = $db->id_history;
734     local $id_history_db->{version_control} = $vc;
735    
736     my $names_history_db = $db->name_history;
737     local $names_history_db->{version_control} = $vc;
738 wakaba 1.2
739     my $time = time;
740    
741 wakaba 1.1 $names_lock->lock;
742    
743     my $id_prop = $id_prop_db->get_data ($id);
744     my $old_names = $id_prop->{name} || {};
745    
746     my $new_names = {};
747     for (split /\x0D\x0A?|\x0A/, $cgi->get_parameter ('names')) {
748     $new_names->{normalize_name ($_)} = 1;
749     }
750     delete $new_names->{''};
751     $new_names->{'(no title)'} = 1 unless keys %$new_names;
752    
753     my $added_names = {};
754     my $removed_names = {%$old_names};
755     for (keys %$new_names) {
756     if ($old_names->{$_}) {
757     delete $removed_names->{$_};
758     } else {
759     $added_names->{$_} = 1;
760     }
761     }
762    
763     for my $new_name (keys %$added_names) {
764     my $new_name_prop = $name_prop_db->get_data ($new_name);
765     unless (defined $new_name_prop) {
766     my $sw3id = $sw3_pages->get_data ($new_name);
767     convert_sw3_page ($sw3id => $new_name);
768     $new_name_prop = $name_prop_db->get_data ($new_name);
769 wakaba 1.2 unless (defined $new_name_prop) {
770     $names_history_db->append_data ($new_name => [$time, 'c']);
771     }
772 wakaba 1.1 }
773     $new_name_prop->{name} = $new_name;
774     push @{$new_name_prop->{id} ||= []}, $id;
775     $name_prop_db->set_data ($new_name => $new_name_prop);
776 wakaba 1.2
777     $id_history_db->append_data ($id => [$time, 'a', $new_name]);
778     $names_history_db->append_data ($new_name => [$time, 'a', $id]);
779 wakaba 1.1 }
780    
781     for my $removed_name (keys %$removed_names) {
782     my $removed_name_prop = $name_prop_db->get_data ($removed_name);
783     for (0..$#{$removed_name_prop->{id} or []}) {
784     if ($removed_name_prop->{id}->[$_] eq $id) {
785     splice @{$removed_name_prop->{id}}, $_, 1, ();
786     last;
787     }
788     }
789     $name_prop_db->set_data ($removed_name => $removed_name_prop);
790 wakaba 1.2
791     $id_history_db->append_data ($id => [$time, 'r', $removed_name]);
792     $names_history_db->append_data ($removed_name => [$time, 'r', $id]);
793 wakaba 1.1 }
794    
795     $id_prop->{name} = $new_names;
796     $id_prop_db->set_data ($id => $id_prop);
797    
798     my $user = $cgi->remote_user // '(anon)';
799     $vc->commit_changes ("id-name association changed by $user");
800    
801     $names_lock->unlock;
802    
803     print "Status: 204 Changed\n\n";
804    
805     exit;
806     } else {
807     http_error (405, 'Method not allowed', 'PUT');
808     }
809 wakaba 1.2 } elsif ($param eq 'history' and not defined $dollar) {
810     my $id = $path[1] + 0;
811    
812 wakaba 1.27 my $id_history_db = $db->id_history;
813 wakaba 1.2 my $history = $id_history_db->get_data ($id);
814    
815     binmode STDOUT, ':encoding(utf-8)';
816     print "Content-Type: text/html; charset=utf-8\n\n";
817    
818     my $doc = $dom->create_document;
819     $doc->manakai_is_html (1);
820     $doc->inner_html (q[<!DOCTYPE HTML><html lang=en><title></title><h1></h1>
821     <div class=section><h2>History</h2><table>
822    
823     <thead>
824     <tr><th scope=col>Time<th scope=col>Change
825    
826     <tbody>
827    
828     </table></div>]);
829     set_head_content ($doc, undef, [], []);
830    
831     my $title_el = $doc->get_elements_by_tag_name ('title')->[0];
832     $title_el->inner_html ('History &mdash; #');
833     $title_el->manakai_append_text ($id);
834    
835     my $h1_el = $doc->get_elements_by_tag_name ('h1')->[0];
836     $h1_el->text_content ('#' . $id);
837    
838     my $table_el = $doc->get_elements_by_tag_name ('table')->[0];
839     if ($history) {
840     my $tbody_el = $table_el->last_child;
841    
842     for my $entry (@$history) {
843     my $tr_el = $doc->create_element_ns (HTML_NS, 'tr');
844    
845     my $date_cell = $doc->create_element_ns (HTML_NS, 'td');
846     my $date = gmtime ($entry->[0] || 0); ## TODO: ...
847     $date_cell->inner_html ('<time>' . $date . '</time>');
848     $tr_el->append_child ($date_cell);
849    
850     my $change_cell = $doc->create_element_ns (HTML_NS, 'td');
851     if ($entry->[1] eq 'c') {
852     $change_cell->manakai_append_text ('Created');
853     } elsif ($entry->[1] eq 'a') {
854     $change_cell->manakai_append_text ('Associated with ');
855     my $a_el = $doc->create_element_ns (HTML_NS, 'a');
856     $a_el->set_attribute (href => get_page_url ($entry->[2], undef)
857     . ';history');
858     $a_el->text_content ($entry->[2]);
859     $change_cell->append_child ($a_el);
860     } elsif ($entry->[1] eq 'r') {
861     $change_cell->manakai_append_text ('Disassociated from ');
862     my $a_el = $doc->create_element_ns (HTML_NS, 'a');
863     $a_el->set_attribute (href => get_page_url ($entry->[2], undef)
864     . ';history');
865     $a_el->text_content ($entry->[2]);
866     $change_cell->append_child ($a_el);
867     } elsif ($entry->[1] eq 't') {
868     $change_cell->manakai_append_text
869     ('Converted from SuikaWiki3 database');
870     } else {
871     $change_cell->manakai_append_text ($entry->[1]);
872     }
873     $tr_el->append_child ($change_cell);
874    
875     $tbody_el->append_child ($tr_el);
876     }
877     } else {
878     my $p_el = $doc->create_element_ns (HTML_NS, 'p');
879     $p_el->text_content ('No history data.');
880     $table_el->parent_node->replace_child ($p_el, $table_el);
881     }
882    
883 wakaba 1.18 set_foot_content ($doc);
884    
885 wakaba 1.2 print $doc->inner_html;
886     exit;
887 wakaba 1.5 } elsif ($param eq 'terms' and not defined $dollar) {
888     my $id = $path[1] + 0;
889 wakaba 1.20
890     ## This must be done before the ID lock.
891     $db->name_inverted_index->lock;
892 wakaba 1.5
893     my $id_lock = $id_locks->get_lock ($id);
894     $id_lock->lock;
895    
896     my $id_prop = $id_prop_db->get_data ($id);
897     my $cache_prop = $cache_prop_db->get_data ($id);
898     my $doc = $id_prop ? get_xml_data ($id, $id_prop, $cache_prop) : undef;
899    
900     if (defined $doc) {
901     update_tfidf ($id, $doc);
902 wakaba 1.20
903     $id_lock->unlock;
904 wakaba 1.27
905 wakaba 1.5 binmode STDOUT, ':encoding(utf-8)';
906     print "Content-Type: text/plain; charset=utf-8\n\n";
907 wakaba 1.27
908 wakaba 1.7 print ${ $db->id_tfidf->get_data ($id) };
909 wakaba 1.5
910     exit;
911 wakaba 1.20 } else {
912     $id_lock->unlock;
913 wakaba 1.5 }
914 wakaba 1.8 } elsif ($param =~ /^(un)?related-([0-9]+)$/ and not defined $dollar) {
915     my $id1 = $path[1] + 0;
916     my $id2 = $2 + 0;
917     my $answer = $1 ? -1 : 1;
918    
919 wakaba 1.14 require SWE::Object::Repository;
920     my $repo = SWE::Object::Repository->new (db => $db);
921 wakaba 1.8
922 wakaba 1.14 my $y = $repo->are_related_ids ($id1, $id2, $answer);
923 wakaba 1.8
924 wakaba 1.14 $repo->save_term_weight_vector;
925 wakaba 1.8
926     binmode STDOUT, ':encoding(utf-8)';
927     print "Content-Type: text/plain; charset=utf-8\n\n";
928    
929 wakaba 1.14 print "$y\n\n";
930     # print $diff->stringify, "\n\n";
931     #
932     # print $fv1->stringify, "\n\n";
933     # print $fv2->stringify, "\n\n";
934 wakaba 1.8 exit;
935 wakaba 1.1 }
936     } elsif ($path[0] eq 'new-page' and @path == 1) {
937     if ($cgi->request_method eq 'POST') {
938     my $new_names = {};
939     for (split /\x0D\x0A?|\x0A/, scalar $cgi->get_parameter ('names')) {
940     $new_names->{normalize_name ($_)} = 1;
941     }
942     delete $new_names->{''};
943     $new_names->{'(no title)'} = 1 unless keys %$new_names;
944    
945     my $user = '(anon)'; #$cgi->remote_user // '(anon)';
946     my $ct = get_content_type_parameter ();
947    
948     my $content = $cgi->get_parameter ('text') // '';
949     normalize_content (\$content);
950    
951     $names_lock->lock;
952 wakaba 1.2 my $time = time;
953 wakaba 1.1
954 wakaba 1.29 require SWE::Object::Document;
955 wakaba 1.30 my $document = SWE::Object::Document->new_id (db => $db);
956 wakaba 1.29 $document->{name_prop_db} = $name_prop_db; ## TODO: ...
957     $document->{sw3_pages} = $sw3_pages; ## TODO: ...
958    
959 wakaba 1.30 my $id = $document->id;
960    
961 wakaba 1.1 {
962 wakaba 1.22 ## This must be done before the ID lock.
963     $db->name_inverted_index->lock;
964    
965 wakaba 1.1 my $id_lock = $id_locks->get_lock ($id);
966     $id_lock->lock;
967 wakaba 1.26
968     my $vc = $db->vc;
969 wakaba 1.1 local $content_db->{version_control} = $vc;
970     local $id_prop_db->{version_control} = $vc;
971 wakaba 1.30 $vc->add_file ($db->id->{file_name});
972 wakaba 1.1
973 wakaba 1.27 my $id_history_db = $db->id_history;
974     local $id_history_db->{version_control} = $vc;
975 wakaba 1.2
976 wakaba 1.1 $content_db->set_data ($id => \$content);
977    
978     my $id_props = {};
979 wakaba 1.2
980     $id_history_db->append_data ($id => [$time, 'c']);
981     $id_props->{modified} = $time;
982    
983     for (keys %$new_names) {
984     $id_props->{name}->{$_} = 1;
985     $id_history_db->append_data ($id => [$time, 'a', $_]);
986     }
987    
988 wakaba 1.1 $id_props->{'content-type'} = $ct;
989     $id_props->{hash} = get_hash (\$content);
990     $id_props->{title} = $cgi->get_parameter ('title') // '';
991     normalize_content (\($id_props->{title}));
992     $id_props->{'title-type'} = 'text/plain'; ## TODO: get_parameter
993     $id_prop_db->set_data ($id => $id_props);
994    
995     $vc->commit_changes ("created by $user");
996    
997 wakaba 1.22 ## TODO: non-default content-type support
998     my $cache_prop = $cache_prop_db->get_data ($id);
999     my $doc = $id_props ? get_xml_data ($id, $id_props, $cache_prop) : undef;
1000    
1001     if (defined $doc) {
1002 wakaba 1.29 $document->update_tfidf ($doc);
1003 wakaba 1.22 }
1004    
1005 wakaba 1.1 $id_lock->unlock;
1006     }
1007    
1008 wakaba 1.28 $document->associate_names ($new_names, user => $user, time => $time);
1009 wakaba 1.1
1010     my $url = get_page_url ([keys %$new_names]->[0], undef, 0 + $id);
1011     http_redirect (301, 'Created', $url);
1012     } else {
1013     binmode STDOUT, ':encoding(utf8)';
1014     print "Content-Type: text/html; charset=utf-8\n\n";
1015    
1016     ## TODO: select name=title-type
1017     my $doc = $dom->create_document;
1018     $doc->manakai_is_html (1);
1019     $doc->inner_html (q[<!DOCTYPE HTML><title>New page</title>
1020     <h1>New page</h1>
1021     <form action=new-page method=post accept-charset=utf-8>
1022     <p><button type=submit>Save</button>
1023     <p><label><strong>Page name(s)</strong>:<br>
1024     <textarea name=names></textarea></label>
1025     <p><label><strong>Page title</strong>:<br>
1026     <input name=title></label>
1027     <p><label for=page-body-text><strong>Page body</strong></label>:
1028     <span class=text-toolbar></span><br>
1029     <textarea name=text id=page-body-text></textarea>
1030     <p><button type=submit>Save</button>
1031     <select name=content-type></select>
1032 wakaba 1.3 [<a rel=help>Help</a> / <a rel=license>License</a>]
1033 wakaba 1.1 </form>
1034     ]);
1035     set_head_content ($doc, undef, [],
1036     [{name => 'ROBOTS', content => 'NOINDEX'}]);
1037    
1038     my $form_el = $doc->get_elements_by_tag_name ('form')->[0];
1039     set_content_type_options
1040     ($doc, $form_el->get_elements_by_tag_name ('select')->[0]);
1041    
1042     my $names = $cgi->get_parameter ('names') // '';
1043     $form_el->get_elements_by_tag_name ('textarea')->[0]
1044     ->text_content ($names);
1045    
1046     my $a_el = $form_el->get_elements_by_tag_name ('a')->[0];
1047 wakaba 1.3 our $help_page_name;
1048     $a_el->set_attribute (href => get_page_url ($help_page_name));
1049    
1050     $a_el = $form_el->get_elements_by_tag_name ('a')->[1];
1051 wakaba 1.1 our $license_name;
1052     $a_el->set_attribute (href => get_page_url ($license_name));
1053    
1054 wakaba 1.18 set_foot_content ($doc);
1055    
1056 wakaba 1.1 print $doc->inner_html;
1057     exit;
1058     }
1059 wakaba 1.11 } elsif (@path == 2 and $path[0] eq 'g') {
1060     my $id = 0+$path[1];
1061    
1062     if ($path[1] =~ /\A([0-9]+)\z/ and not defined $dollar) {
1063     #
1064     } elsif ($path[1] =~ /^id([0-9]+)$/ and not defined $dollar) {
1065     my $docid = 0+$1;
1066    
1067     ## TODO: ID lock
1068    
1069     my $id_prop = $id_prop_db->get_data ($docid);
1070 wakaba 1.9
1071 wakaba 1.11 $id = $id_prop->{node_id};
1072    
1073     unless (defined $id) {
1074     require SWE::Object::Graph;
1075     my $graph = SWE::Object::Graph->new (db => $db);
1076     my $node = $graph->create_node ($docid, $id_prop_db);
1077    
1078 wakaba 1.12 $id = $node->id;
1079 wakaba 1.11
1080     $id_prop->{node_id} = $id;
1081     $id_prop_db->set_data ($docid => $id_prop);
1082     }
1083 wakaba 1.9 }
1084 wakaba 1.11
1085 wakaba 1.10 use Data::Dumper;
1086     binmode STDOUT, ':encoding(utf-8)';
1087     print "Content-Type: text/plain; charset=UTF-8\n\n";
1088    
1089 wakaba 1.11 my $node_prop = $db->graph_prop->get_data ($id);
1090 wakaba 1.10
1091 wakaba 1.11 print Dumper $node_prop;
1092 wakaba 1.10
1093 wakaba 1.12 require SWE::Object::Graph;
1094     my $graph = SWE::Object::Graph->new (db => $db);
1095     my $node = $graph->get_node_by_id ($id);
1096    
1097 wakaba 1.13 my $neighbors = [map {
1098     my $o = $_;
1099     $o->{doc_id} = $o->{node}->document_id;
1100     $o;
1101     } map {
1102     {
1103     node_id => $_,
1104     node => $graph->get_node_by_id ($_),
1105     }
1106 wakaba 1.15 } $id, keys %{$node->neighbor_ids}];
1107    
1108     require SWE::Object::Repository;
1109     my $repo = SWE::Object::Repository->new (db => $db);
1110     my $doc_id = $node->document_id;
1111 wakaba 1.12
1112 wakaba 1.13 for my $n (@$neighbors) {
1113     if ($n->{doc_id}) {
1114     my $id_prop = $id_prop_db->get_data ($n->{doc_id});
1115     print $n->{node_id}, "\t", $n->{doc_id}, "\t",
1116     (length $id_prop->{title} ? $id_prop->{title}
1117     : [keys %{$id_prop->{name}}]->[0] // ''); ## TODO: title-type
1118 wakaba 1.16 print "\t", ($repo->are_related_ids ($doc_id, $n->{doc_id}) // 'u' || '0')
1119 wakaba 1.15 if defined $doc_id;
1120 wakaba 1.13 print "\n";
1121     } else {
1122     print $n->{node_id}, "\n";
1123     }
1124     }
1125 wakaba 1.12
1126 wakaba 1.10 exit;
1127 wakaba 1.1 } elsif (@path == 1 and
1128     {'' => 1, 'n' => 1, 'i' => 1}->{$path[0]}) {
1129     our $homepage_name;
1130     http_redirect (303, 'See other', get_page_url ($homepage_name, undef));
1131     } elsif (@path == 0) {
1132     my $rurl = $cgi->request_uri;
1133     $rurl =~ s!\.[^/]*$!!g;
1134     http_redirect (303, 'See other', $rurl . '/');
1135     }
1136    
1137     http_error (404, 'Not found');
1138    
1139 wakaba 1.23 sub prepare_by_name ($$) {
1140     my ($name, $id_cand) = @_;
1141    
1142     my $ids = get_ids_by_name ($name);
1143     unless (ref $ids) {
1144     $names_lock->lock;
1145     $sw3_pages->reset;
1146    
1147     $ids = convert_sw3_page ($ids => $name);
1148     $names_lock->unlock;
1149     }
1150    
1151     my $id;
1152     if (defined $id_cand) {
1153     $id_cand += 0;
1154     for (0..$#$ids) {
1155     if ($ids->[$_] == $id_cand) {
1156     $id = $id_cand;
1157     splice @$ids, $_, 1, ();
1158     last;
1159     }
1160     }
1161     } else {
1162     $id = shift @$ids;
1163     }
1164    
1165     return ($id, $ids);
1166     } # prepare_by_name
1167    
1168 wakaba 1.1 sub get_content_type_parameter () {
1169     my $ct = $cgi->get_parameter ('content-type') // 'text/x-suikawiki';
1170    
1171     my $valid_ct;
1172 wakaba 1.38 for (@ContentMediaType) {
1173 wakaba 1.1 if ($_->{type} eq $ct) {
1174     $valid_ct = 1;
1175     last;
1176     }
1177     }
1178     unless ($valid_ct) {
1179     http_error (400, 'content-type parameter not allowed');
1180     ## TODO: 406?
1181     }
1182    
1183     return $ct;
1184     } # get_content_type_parameter
1185    
1186     sub set_content_type_options ($$;$) {
1187     my ($doc, $select_el, $ct) = @_;
1188     $ct //= 'text/x-suikawiki';
1189    
1190     my $has_ct;
1191 wakaba 1.38 for (@ContentMediaType) {
1192 wakaba 1.1 next unless defined $_->{label};
1193     my $option_el = $doc->create_element_ns (HTML_NS, 'option');
1194     $option_el->set_attribute (value => $_->{type});
1195     $option_el->text_content ($_->{label});
1196     if ($_->{type} eq $ct) {
1197     $option_el->set_attribute (selected => '');
1198     $has_ct = 1;
1199     }
1200     $select_el->append_child ($option_el);
1201     }
1202     unless ($has_ct) {
1203     my $option_el = $doc->create_element_ns (HTML_NS, 'option');
1204     $option_el->set_attribute (value => $ct);
1205     $option_el->text_content ($ct);
1206     $option_el->set_attribute (selected => '');
1207     $select_el->append_child ($option_el);
1208     }
1209     } # set_content_type_options
1210    
1211     sub http_error ($$;$) {
1212     my ($code, $text, $allowed) = @_;
1213     binmode STDOUT, ":encoding(utf-8)";
1214    
1215     our $style_url;
1216    
1217     print qq[Status: $code $text\n];
1218     print qq[Allow: $allowed\n] if defined $allowed;
1219     print qq[Content-Type: text/html; charset=utf-8\n\n];
1220     print qq[<!DOCTYPE HTML>
1221     <html lang=en><title>$code @{[htescape ($text)]}</title>
1222     <link rel=stylesheet href="@{[htescape ($style_url)]}">
1223     <h1>@{[htescape ($text)]}</h1>];
1224     exit;
1225     } # http_error
1226    
1227     sub http_redirect ($$$) {
1228     my ($code, $text, $url) = @_;
1229    
1230     my $abs_url = get_absolute_url ($url, $cgi->request_uri);
1231    
1232     binmode STDOUT, ':encoding(utf-8)';
1233     print qq[Status: $code $text
1234     Location: $abs_url
1235     Content-Type: text/html; charset=utf-8
1236    
1237     <!DOCTYPE HTML>
1238     <html lang=en>
1239     <title>$code @{[htescape ($text)]}</title>
1240     <h1>@{[htescape ($text)]}</h1>
1241     <p>See <a href="@{[htescape ($url)]}">other page</a>.];
1242     exit;
1243     } # http_redirect
1244    
1245     sub normalize_name ($) {
1246     my $s = shift;
1247     Char::Normalize::FullwidthHalfwidth::normalize_width (\$s);
1248     $s =~ s/\s+/ /g;
1249     $s =~ s/^ //;
1250     $s =~ s/ $//;
1251     return $s;
1252     } # normalize_name
1253    
1254     sub normalize_content ($) {
1255     my $sref = shift;
1256     Char::Normalize::FullwidthHalfwidth::normalize_width ($sref);
1257     } # normalize_content
1258    
1259     ## A source anchor label in SWML -> URL
1260     sub get_page_url ($$;$) {
1261     my ($wiki_name, $base_name, $id) = @_;
1262     $wiki_name = percent_encode ($wiki_name);
1263     $wiki_name =~ s/%2F/+/g;
1264     if (defined $id) {
1265     $wiki_name .= '$' . (0 + $id);
1266     }
1267     $wiki_name = ('../' x (@path - 1)) . 'n/' . $wiki_name;
1268     return $wiki_name;
1269     } # get_page_url
1270    
1271     sub get_xml_data ($$$) {
1272     my ($id, $id_prop, $cache_prop) = @_;
1273    
1274     my $cached_hash = $cache_prop->{'cached-hash'};
1275    
1276     if ($cached_hash) {
1277     my $content_hash = $id_prop->{hash} || '';
1278    
1279     if ($cached_hash ne $content_hash) {
1280     undef $cached_hash;
1281     }
1282     }
1283    
1284     state $content_cache_db;
1285     unless (defined $content_cache_db) {
1286     require SWE::DB::IDDOM;
1287     $content_cache_db = SWE::DB::IDDOM->new;
1288     $content_cache_db->{root_directory_name} = $db_id_dir_name;
1289     $content_cache_db->{leaf_suffix} = '.domcache';
1290     }
1291    
1292     my $doc;
1293     if ($cached_hash) {
1294     $doc = $content_cache_db->get_data ($id);
1295     } else {
1296     my $textref = $content_db->get_data ($id);
1297     if ($textref) {
1298     require Whatpm::SWML::Parser;
1299     my $p = Whatpm::SWML::Parser->new;
1300    
1301     $doc = $dom->create_document;
1302     $p->parse_char_string ($$textref => $doc);
1303    
1304     $content_cache_db->set_data ($id => $doc);
1305    
1306     $cache_prop->{'cached-hash'} = get_hash ($textref);
1307     $cache_prop_db->set_data ($id => $cache_prop);
1308     } else {
1309     ## Content not found.
1310     $doc = $dom->create_document;
1311     }
1312     }
1313    
1314     return $doc;
1315     } # get_xml_data
1316    
1317     sub get_hash ($) {
1318     require Digest::MD5;
1319     return Digest::MD5::md5_hex (Encode::encode ('utf8', ${$_[0]}));
1320     } # get_hash
1321    
1322     sub get_ids_by_name ($) {
1323     my $name = shift;
1324    
1325     my $name_prop = $name_prop_db->get_data ($name);
1326    
1327     if ($name_prop->{id}) {
1328     return $name_prop->{id};
1329     } else {
1330     my $sw3id = $sw3_pages->get_data ($name);
1331    
1332     if (defined $sw3id) {
1333     return $sw3id; # not an arrayref
1334     } else {
1335     return [];
1336     }
1337     }
1338     } # get_ids_by_name
1339    
1340 wakaba 1.5 sub for_unique_words ($*) {
1341     #my ($string, $code) = @_;
1342    
1343     ## TODO: use mecab
1344    
1345     require Text::Kakasi;
1346     my $k = Text::Kakasi->new;
1347    
1348     ## TODO: support stop words
1349    
1350     my $all_terms = 0;
1351     my $terms = {};
1352     for my $term (split /\s+/, $k->set (qw/-iutf8 -outf8 -w/)->get ($_[0])) {
1353    
1354 wakaba 1.7 ## TODO: provide a way to save original representation
1355    
1356 wakaba 1.5 ## TODO: more normalization
1357     $term = lc $term;
1358    
1359     $terms->{$term}++;
1360     }
1361    
1362     for my $term (keys %$terms) {
1363     $_[1]->($term, $terms->{$term});
1364     }
1365     } # for_unique_words
1366    
1367 wakaba 1.29 ## TODO: This function is OBSOLETE!
1368 wakaba 1.5 sub update_tfidf ($$) {
1369     my ($id, $doc) = @_;
1370    
1371 wakaba 1.29 require SWE::Object::Document;
1372     my $document = SWE::Object::Document->new (db => $db, id => $id);
1373     $document->{name_prop_db} = $name_prop_db; ## TODO: ...
1374     $document->{sw3_pages} = $sw3_pages; ## TODO: ...
1375 wakaba 1.6
1376 wakaba 1.29 $document->update_tfidf ($doc);
1377 wakaba 1.5 } # update_tfidf
1378    
1379 wakaba 1.1 sub convert_sw3_page ($$) {
1380     my ($sw3key => $name) = @_;
1381    
1382     my $page_key = $sw3_pages->get_data ($name);
1383     ## NOTE: $page_key is undef if the page has been converted
1384     ## between the first (in get_ids_by_name) and the second (the
1385     ## line above) $sw3_pages->get_data calls.
1386    
1387     my $ids;
1388     if (defined $page_key) {
1389 wakaba 1.9 my $idgen = $db->id;
1390 wakaba 1.2 my $time = time;
1391 wakaba 1.1
1392     my $id = $idgen->get_next_id;
1393     my $id_lock = $id_locks->get_lock ($id);
1394     $id_lock->lock;
1395    
1396 wakaba 1.26 my $vc = $db->vc;
1397 wakaba 1.1 local $content_db->{version_control} = $vc;
1398     local $id_prop_db->{version_control} = $vc;
1399     $vc->add_file ($idgen->{file_name});
1400    
1401 wakaba 1.27 my $id_history_db = $db->id_history;
1402     local $id_history_db->{version_control} = $vc;
1403    
1404 wakaba 1.1 our $sw3_db_dir_name;
1405    
1406     state $sw3_content_db;
1407     unless (defined $sw3_content_db) {
1408     require SWE::DB::SuikaWiki3;
1409     $sw3_content_db = SWE::DB::SuikaWiki3->new;
1410     $sw3_content_db->{root_directory_name} = $sw3_db_dir_name;
1411     }
1412    
1413     state $sw3_prop_db;
1414     unless (defined $sw3_prop_db) {
1415     require SWE::DB::SuikaWiki3Props;
1416     $sw3_prop_db = SWE::DB::SuikaWiki3Props->new;
1417     $sw3_prop_db->{root_directory_name} = $sw3_db_dir_name;
1418     }
1419    
1420     state $sw3_lm_db;
1421     unless (defined $sw3_lm_db) {
1422     require SWE::DB::SuikaWiki3LastModified;
1423     $sw3_lm_db = SWE::DB::SuikaWiki3LastModified->new;
1424     $sw3_lm_db->{file_name} = $sw3_db_dir_name .
1425     'mt--6C6173745F6D6F646966696564.dat';
1426     }
1427    
1428     my $content = $sw3_content_db->get_data ($page_key);
1429     $content_db->set_data ($id => \$content);
1430    
1431     my $id_props = $sw3_prop_db->get_data ($page_key);
1432     my $lm = $sw3_lm_db->get_data ($name);
1433     $id_props->{name}->{$name} = 1;
1434     $id_props->{modified} = $lm if defined $lm;
1435     $id_props->{'converted-from-sw3'} = time;
1436     $id_props->{'sw3-key'} = $page_key;
1437     $id_props->{hash} = get_hash (\$content);
1438     $id_prop_db->set_data ($id => $id_props);
1439    
1440 wakaba 1.2 $id_history_db->append_data ($id => [$time, 't']);
1441     $id_history_db->append_data ($id => [$time, 'a', $name]);
1442    
1443 wakaba 1.1 $vc->commit_changes ("converted from SuikaWiki3 <http://suika.fam.cx/gate/cvs/suikawiki/wikidata/page/$page_key.txt>");
1444    
1445     $id_lock->unlock;
1446    
1447 wakaba 1.26 $vc = $db->vc;
1448 wakaba 1.1 local $name_prop_db->{version_control} = $vc;
1449     local $sw3_pages->{version_control} = $vc;
1450    
1451 wakaba 1.27 my $names_history_db = $db->name_history;
1452     local $names_history_db->{version_control} = $vc;
1453 wakaba 1.2
1454 wakaba 1.1 my $name_props = $name_prop_db->get_data ($name);
1455     push @{$name_props->{id} ||= []}, $id;
1456     $ids = $name_props->{id};
1457     $name_props->{name} = $name;
1458     $name_prop_db->set_data ($name => $name_props);
1459    
1460 wakaba 1.2 $names_history_db->append_data ($name => [$time, 't']);
1461     $names_history_db->append_data ($name => [$time, 'a', $id]);
1462    
1463 wakaba 1.1 $sw3_pages->delete_data ($name);
1464     $sw3_pages->save_data;
1465    
1466     $vc->commit_changes ("converted from SuikaWiki3 <http://suika.fam.cx/gate/cvs/suikawiki/wikidata/page/$page_key.txt>");
1467     } else {
1468     my $name_props = $name_prop_db->get_data ($name);
1469     $ids = $name_props->{id};
1470     }
1471    
1472     return $ids;
1473     } # convert_sw3_page
1474    
1475     sub set_head_content ($;$$$) {
1476     my ($doc, $id, $links, $metas) = @_;
1477     my $head_el = $doc->manakai_head;
1478    
1479     our $license_name;
1480     our $style_url;
1481     push @{$links ||= []}, {rel => 'stylesheet', href => $style_url},
1482     {rel => 'license', href => get_page_url ($license_name, undef)};
1483    
1484     if (defined $id) {
1485     our $cvs_archives_url;
1486     push @$links, {rel => 'archives',
1487     href => $cvs_archives_url . 'ids/' .
1488 wakaba 1.2 int ($id / 1000) . '/' . ($id % 1000) . '.txt',
1489     title => 'CVS log for the page content'};
1490 wakaba 1.1 }
1491    
1492     for my $item (@$links) {
1493     my $link_el = $doc->create_element_ns (HTML_NS, 'link');
1494     for (keys %$item) {
1495     $link_el->set_attribute ($_ => $item->{$_});
1496     }
1497     $head_el->append_child ($link_el);
1498     }
1499    
1500     for my $item (@{$metas or []}) {
1501     my $meta_el = $doc->create_element_ns (HTML_NS, 'meta');
1502     $meta_el->set_attribute (name => $item->{name} // '');
1503     $meta_el->set_attribute (content => $item->{content} // '');
1504     $head_el->append_child ($meta_el);
1505     }
1506 wakaba 1.18 } # set_head_content
1507    
1508     sub set_foot_content ($) {
1509     my $doc = shift;
1510    
1511     my $body_el = $doc->last_child->last_child;
1512 wakaba 1.1
1513     our $script_url;
1514     my $script_el = $doc->create_element_ns (HTML_NS, 'script');
1515     $script_el->set_attribute (src => $script_url);
1516 wakaba 1.18 $body_el->append_child ($script_el);
1517     } # set_foot_content
1518 wakaba 1.1
1519 wakaba 1.39 1; ## $Date: 2009/09/14 02:13:13 $

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24