/[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.42 - (hide annotations) (download)
Mon Sep 14 06:16:38 2009 UTC (15 years, 7 months ago) by wakaba
Branch: MAIN
Changes since 1.41: +5 -3 lines
File MIME type: text/plain
++ swe/lib/suikawiki/ChangeLog	14 Sep 2009 06:15:55 -0000
	* main.pl: Added names="" parameter to canvas editor link.

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

++ swe/pages/ChangeLog	14 Sep 2009 06:16:20 -0000
	* canvas.html: Added names="" parameter to specify the names of
	new images.

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

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24  
Google Analytics is used in this page; Cookies are used. 忍者AdMax is used in this page; Cookies are used. Privacy policy.