]> git.p6c8.net - selfforum.git/blob - selfforum-cgi/shared/Posting/_lib.pm
3d082ac6fe6ea06ffb69ad9996a6ced0f1983fa8
[selfforum.git] / selfforum-cgi / shared / Posting / _lib.pm
1 package Posting::_lib;
2
3 ################################################################################
4 # #
5 # File: shared/Posting/_lib.pm #
6 # #
7 # Authors: André Malo <nd@o3media.de>, 2001-03-03 #
8 # Frank Schoenmann <fs@tower.de>, 2001-03-02 #
9 # #
10 # Description: Message access interface, time format routines #
11 # #
12 ################################################################################
13
14 use strict;
15
16 use vars qw(@EXPORT_OK);
17 use base qw(Exporter);
18
19 use Encode::Plain; $Encode::Plain::utf8 = 1;
20
21 use XML::DOM;
22
23 # ====================================================
24 # Export
25 # ====================================================
26
27 @EXPORT_OK = qw(get_message_header get_message_body get_message_node get_body_node parse_single_thread
28 hr_time short_hr_time long_hr_time
29 get_all_threads create_forum_xml_string
30 save_file);
31
32 # ====================================================
33 # Access via XML::DOM
34 # ====================================================
35
36 ### get_message_header () ######################################################
37 #
38 # Read message header, return as a hash
39 #
40 # Params: $node XML message node
41 # Return: Hash reference (name, category, subject, email, home, image, time)
42 #
43 sub get_message_header ($)
44 {
45 my $node = shift;
46 my %conf;
47
48 my $header = $node -> getElementsByTagName ('Header', 0) -> item (0);
49 my $author = $header -> getElementsByTagName ('Author', 0) -> item (0);
50 my $name = $author -> getElementsByTagName ('Name', 0) -> item (0);
51 my $email = $author -> getElementsByTagName ('Email', 0) -> item (0);
52 my $home = $author -> getElementsByTagName ('HomepageUrl', 0) -> item (0);
53 my $image = $author -> getElementsByTagName ('ImageUrl', 0) -> item (0);
54 my $cat = $header -> getElementsByTagName ('Category', 0) -> item (0);
55 my $subject = $header -> getElementsByTagName ('Subject', 0) -> item (0);
56 my $date = $header -> getElementsByTagName ('Date', 0) -> item (0);
57
58 %conf = (
59 name => ($name -> hasChildNodes)?$name -> getFirstChild -> getData:undef,
60 category => ($cat -> hasChildNodes)?$cat -> getFirstChild -> getData:undef,
61 subject => ($subject -> hasChildNodes)?$subject -> getFirstChild -> getData:undef,
62 email => (defined ($email) and $email -> hasChildNodes)?$email -> getFirstChild -> getData:undef,
63 home => (defined ($home) and $home -> hasChildNodes)?$home -> getFirstChild -> getData:undef,
64 image => (defined ($image) and $image -> hasChildNodes)?$image -> getFirstChild -> getData:undef,
65 time => $date -> getAttribute ('longSec')
66 );
67
68 \%conf;
69 }
70
71 ### get_body_node () ########################################################
72 #
73 # Search a specific message body in a XML tree
74 #
75 # Params: $xml XML::DOM::Document Object (Document Node)
76 # $mid Message ID
77 # Return: MessageContent XML node (or -none-)
78 #
79 sub get_body_node ($$)
80 {
81 my ($xml, $mid) = @_;
82
83 for ($xml->getElementsByTagName ('ContentList', 1)->item (0)->getElementsByTagName ('MessageContent', 0))
84 {
85 return $_ if ($_ -> getAttribute ('mid') eq $mid);
86 }
87
88 return;
89 }
90
91 ### get_message_body () ########################################################
92 #
93 # Read message body
94 #
95 # Params: $xml XML::DOM::Document Object (Document Node)
96 # $mid Message ID
97 # Return: Scalar reference
98 #
99 sub get_message_body ($$)
100 {
101 my $cnode = get_body_node ($_[0], $_[1]);
102 my $body;
103
104 $body = ($cnode -> hasChildNodes)?$cnode -> getFirstChild -> getData:'' if $cnode;
105
106 \$body;
107 }
108
109 ### get_message_node () ########################################################
110 #
111 # Search a specific message in a XML tree
112 #
113 # Params: $xml XML::DOM::Document Object (Document Node)
114 # $tid Thread ID
115 # $mid Message ID
116 # Return: Message XML node, Thread XML node (or -none-)
117 #
118 sub get_message_node ($$$)
119 {
120 my ($xml, $tid, $mid) = @_;
121 my ($mnode, $tnode);
122
123 for ($xml->getElementsByTagName ('Thread'))
124 {
125 if ($_->getAttribute ('id') eq $tid)
126 {
127 $tnode = $_;
128 for ($tnode -> getElementsByTagName ('Message'))
129 {
130 if ($_ -> getAttribute ('id') eq $mid)
131 {
132 $mnode = $_;
133 last;
134 }
135 }
136 last;
137 }
138 }
139
140 wantarray ? ($mnode, $tnode) : $mnode;
141 }
142
143 ###########################
144 # sub parse_single_thread
145 #
146 # einzelne Threaddatei
147 # parsen
148 ###########################
149
150 sub parse_single_thread ($$;$) {
151 my ($tnode, $deleted, $sorted) = @_;
152 my ($header, @msg, %mno);
153
154 for ($tnode -> getElementsByTagName ('Message')) {
155 $header = get_message_header ($_);
156
157 push @msg,{mid => ($_ -> getAttribute ('id') =~ /(\d+)/)[0],
158 ip => $_ -> getAttribute ('ip'),
159 kids => [$_ -> getElementsByTagName ('Message', 0)],
160 answers => $_ -> getElementsByTagName ('Message') -> getLength,
161 deleted => $_ -> getAttribute ('invisible'),
162 archive => $_ -> getAttribute ('archive'),
163 name => plain($header -> {name}),
164 cat => plain($header -> {category} or ''),
165 subject => plain($header -> {subject}),
166 time => plain($header -> {time})};
167 $mno{$_} = $#msg;}
168
169 # Eintraege ergaenzen und korrigieren
170 my $level;
171 $msg[0] -> {level} = 0;
172 for (@msg) {
173 $level = $_ -> {level} + 1;
174 @{$_ -> {kids}} = map {$msg[$mno{$_}] -> {level} = $level; $mno{$_}} @{$_ -> {kids}};}
175
176 # ============
177 # Sortieren und bei Bedarf
178 # geloeschte Messages entfernen
179
180 my $smsg = sort_thread (\@msg, $sorted);
181 delete_messages ($smsg) unless ($deleted);
182
183 $smsg;
184 }
185
186 ###########################
187 # sub create_message_xml
188 #
189 # Message-XML-String
190 # erzeugen
191 ###########################
192
193 sub create_message_xml ($$$) {
194 my ($xml, $msges, $num) = @_;
195
196 my $msg = $msges -> [$num];
197
198 my $message = $xml -> createElement ('Message');
199 $message -> setAttribute ('id', 'm'.$msg -> {mid});
200 $message -> setAttribute ('invisible', '1') if ($msg -> {deleted});
201 $message -> setAttribute ('archive', '1') if ($msg -> {archive});
202
203 # Header erzeugen
204 my $header = $xml -> createElement ('Header');
205
206 # alles inside of 'Header'
207 my $author = $xml -> createElement ('Author');
208
209 my $name = $xml -> createElement ('Name');
210 $name -> addText (toUTF8($msg -> {name}));
211
212 my $email = $xml -> createElement ('Email');
213
214 my $category = $xml -> createElement ('Category');
215 $category -> addText (toUTF8($msg -> {cat}));
216
217 my $subject = $xml -> createElement ('Subject');
218 $subject -> addText (toUTF8($msg -> {subject}));
219
220 my $date = $xml -> createElement ('Date');
221 $date -> setAttribute ('longSec', $msg -> {time});
222
223 $author -> appendChild ($name);
224 $author -> appendChild ($email);
225 $header -> appendChild ($author);
226 $header -> appendChild ($category);
227 $header -> appendChild ($subject);
228 $header -> appendChild ($date);
229 $message -> appendChild ($header);
230
231 if ($msg -> {kids}) {
232 for (@{$msg -> {kids}}) {
233 $message -> appendChild (&create_message_xml ($xml, $msges, $_));
234 }
235 }
236
237 $message;
238 }
239
240 # ====================================================
241 # XML-Parsen von Hand
242 # ====================================================
243
244 ###########################
245 # sub sort_thread
246 #
247 # Messages eines
248 # Threads sortieren
249 ###########################
250
251 sub sort_thread ($$) {
252 my ($msg, $sorted) = @_;
253
254 my ($z, %mhash) = (0);
255
256 if ($sorted) { # aelteste zuerst
257 for (@$msg) {
258 @$msg[@{$_ -> {kids}}] = sort {$a -> {mid} <=> $b -> {mid}} @$msg[@{$_ -> {kids}}] if (@{$_ -> {kids}} > 1);
259 $mhash{$_ -> {mid}} = [@$msg[@{$_ -> {kids}}]];}}
260
261 else { # juengste zuerst
262 for (@$msg) {
263 @$msg[@{$_ -> {kids}}] = sort {$b -> {mid} <=> $a -> {mid}} @$msg[@{$_ -> {kids}}] if (@{$_ -> {kids}} > 1);
264 $mhash{$_ -> {mid}} = [@$msg[@{$_ -> {kids}}]];}}
265
266 # Kinder wieder richtig einsortieren
267 my @smsg = ($msg -> [0]);
268 for (@smsg) {
269 ++$z;
270 splice @smsg,$z,0,@{$mhash{$_ -> {mid}}} if ($_ -> {answers});
271 delete $_ -> {kids};}
272
273 \@smsg;
274 }
275
276 ### delete_messages () ##########################################################
277 #
278 # Filter out deleted messages
279 #
280 # Params: $smsg Reference of array of references of hashs
281 # Return: -none-
282 #
283 sub delete_messages ($)
284 {
285 my $smsg = shift;
286
287 my ($z, $oldlevel, @path) = (0,0,0);
288
289 for (@$smsg)
290 {
291 if ($_ -> {'deleted'})
292 {
293 my $n = $_ -> {'answers'} + 1;
294 $smsg -> [$_] -> {'answers'} -= $n for (@path);
295 splice @$smsg,$z,$n;
296 }
297 else
298 {
299 if ($_ -> {'level'} > $oldlevel)
300 {
301 push @path,$z;
302 $oldlevel = $_ -> {'level'};
303 }
304 elsif ($_ -> {'level'} < $oldlevel)
305 {
306 splice @path,$_ -> {'level'} - $oldlevel;
307 $oldlevel = $_ -> {'level'};
308 }
309 else
310 {
311 $path[-1] = $z;
312 }
313
314 $z++;
315 }
316 }
317
318 return;
319 }
320
321 ### get_all_threads () ##########################################################
322 #
323 # Read and Parse the main file (without any XML-module, they are too slow)
324 #
325 # Params: $file /path/to/filename of the main file
326 # $deleted hold deleted (invisible) messages in result (1) oder not (0)
327 # $sorted direction of message sort: descending (0) (default) or ascending (1)
328 # Return: scalar context: hash reference
329 # list context: list (\%threads, $last_thread, $last_message, \@unids)
330 #
331 sub get_all_threads ($$;$)
332 {
333 my ($file, $deleted, $sorted) = @_;
334 my ($last_thread, $last_message, @unids, %threads);
335 local (*FILE, $/);
336
337 open FILE, $file or return undef;
338 my $xml = join '', <FILE>;
339 close(FILE) or return undef;
340
341 if (wantarray)
342 {
343 ($last_thread) = map {/(\d+)/} $xml =~ /<Forum.+?lastThread="([^"]+)"[^>]*>/;
344 ($last_message) = map {/(\d+)/} $xml =~ /<Forum.+?lastMessage="([^"]+)"[^>]*>/;
345 }
346
347 my $reg_msg = qr~(?:</Message>
348 |<Message\s+id="m(\d+)"\s+unid="([^"]*)"(?:\s+invisible="([^"]*)")?(?:\s+archive="([^"]*)")?[^>]*>\s*
349 <Header>[^<]*(?:<(?!Name>)[^<]*)*
350 <Name>([^<]+)</Name>[^<]*(?:<(?!Category>)[^<]*)*
351 <Category>([^<]*)</Category>\s*
352 <Subject>([^<]+)</Subject>\s*
353 <Date\s+longSec="(\d+)"[^>]*>\s*
354 </Header>\s*(?:(<)/Message>|(?=(<)Message\s*)))~sx;
355
356 while ($xml =~ /<Thread id="t(\d+)">([^<]*(?:<(?!\/Thread>)[^<]*)*)<\/Thread>/g)
357 {
358 my ($tid, $thread) = ($1, $2);
359 my ($level, $cmno, @msg, @stack) = (0);
360
361 while ($thread =~ m;$reg_msg;g)
362 {
363 if (defined($10))
364 {
365 push @stack,$cmno if (defined $cmno);
366 push @msg, {mid => $1,
367 unid => $2,
368 deleted => $3,
369 archive => $4,
370 name => $5,
371 cat => $6,
372 subject => $7,
373 time => $8,
374 level => $level++,
375 unids => [],
376 kids => [],
377 answers => 0};
378
379 if (defined $cmno)
380 {
381 push @{$msg[$cmno] -> {kids}} => $#msg;
382 push @{$msg[$cmno] -> {unids}} => $2;
383 }
384 else
385 {
386 push @unids => $2;
387 }
388
389 $msg[$_] -> {answers}++ for (@stack);
390
391 $cmno=$#msg;
392
393 $msg[-1] -> {name} =~ s/&amp;/&/g;
394 $msg[-1] -> {cat} =~ s/&amp;/&/g;
395 $msg[-1] -> {subject} =~ s/&amp;/&/g;
396
397 }
398 elsif (defined ($9))
399 {
400 push @msg, {mid => $1,
401 unid => $2,
402 deleted => $3,
403 archive => $4,
404 name => $5,
405 cat => $6,
406 subject => $7,
407 time => $8,
408 level => $level,
409 unids => [],
410 kids => [],
411 answers => 0};
412
413 if (defined $cmno)
414 {
415 push @{$msg[$cmno] -> {kids}} => $#msg;
416 push @{$msg[$cmno] -> {unids}} => $2;
417 $msg[$cmno] -> {answers}++;
418 }
419 else
420 {
421 push @unids => $2;
422 }
423
424 $msg[$_] -> {answers}++ for (@stack);
425
426 $msg[-1] -> {name} =~ s/&amp;/&/g;
427 $msg[-1] -> {cat} =~ s/&amp;/&/g;
428 $msg[-1] -> {subject} =~ s/&amp;/&/g;
429 }
430 else
431 {
432 $cmno = pop @stack; $level--;
433 }
434 }
435
436 my $smsg = sort_thread (\@msg, $sorted); # sort messages
437 delete_messages ($smsg) unless ($deleted); # remove invisible messages
438
439 $threads{$tid} = $smsg if (@$smsg);
440 }
441
442 wantarray ?
443 (\%threads, $last_thread, $last_message, \@unids)
444 : \%threads;
445 }
446
447 ###########################
448 # sub create_forum_xml_string
449 #
450 # Forumshauptdatei erzeugen
451 ###########################
452
453 sub create_forum_xml_string ($$) {
454 my ($threads, $param) = @_;
455 my ($level, $thread, $msg);
456
457 my $xml = '<?xml version="1.0" encoding="UTF-8"?>'."\n"
458 .'<!DOCTYPE Forum SYSTEM "'.$param -> {dtd}.'">'."\n"
459 .'<Forum lastMessage="'.$param -> {lastMessage}.'" lastThread="'.$param -> {lastThread}.'">';
460
461 foreach $thread (sort {$b <=> $a} keys %$threads) {
462 $xml .= '<Thread id="t'.$thread.'">';
463 $level = -1;
464
465 foreach $msg (@{$threads -> {$thread}}) {
466 $xml .= '</Message>' x ($level - $msg -> {level} + 1) if ($msg -> {level} <= $level);
467 $level = $msg -> {level};
468 $xml .= '<Message id="m'.$msg -> {mid}.'"'
469 .' unid="'.$msg -> {unid}.'"'
470 .(($msg -> {deleted})?' invisible="1"':'')
471 .(($msg -> {archive})?' archive="1"':'')
472 .'>'
473 .'<Header>'
474 .'<Author>'
475 .'<Name>'
476 .plain($msg -> {name})
477 .'</Name>'
478 .'<Email></Email>'
479 .'</Author>'
480 .'<Category>'
481 .((length $msg -> {cat})?plain($msg -> {cat}):'')
482 .'</Category>'
483 .'<Subject>'
484 .plain($msg -> {subject})
485 .'</Subject>'
486 .'<Date longSec="'
487 .$msg -> {time}
488 .'"/>'
489 .'</Header>';}
490
491 $xml .= '</Message>' x ($level + 1);
492 $xml .= '</Thread>';}
493
494 $xml.='</Forum>';
495
496 \$xml;
497 }
498
499 ### save_file () ###############################################################
500 #
501 # Save a file
502 #
503 # Params: $filename Filename
504 # $content File content as scalar reference
505 # Return: Status (1 - ok, 0 - error)
506 #
507 sub save_file ($$)
508 {
509 my ($filename, $content) = @_;
510 local *FILE;
511
512 open FILE, ">$filename.temp" or return;
513
514 unless (print FILE $$content)
515 {
516 close FILE;
517 return;
518 }
519
520 close FILE or return;
521
522 rename "$filename.temp", $filename or return;
523
524 1;
525 }
526
527 # ====================================================
528 # Zeitdarstellung
529 # ====================================================
530
531 ###########################
532 # sub hr_time
533 # 02. Januar 2001, 12:02 Uhr
534 #
535 # sub short_hr_time
536 # 02. 01. 2001, 12:02 Uhr
537 #
538 # sub long_hr_time
539 # Dienstag, 02. Januar 2001, 12:02:01 Uhr
540 #
541 # formatierte Zeitangabe
542 ###########################
543
544 sub hr_time ($) {
545 my @month = (qw(Januar Februar), "M\303\244rz", qw(April Mail Juni Juli August September Oktober November Dezember));
546 # ^^^^^^^^ - UTF8 #
547
548 my (undef, $min, $hour, $day, $mon, $year) = localtime ($_[0]);
549
550 sprintf ('%02d. %s %04d, %02d:%02d Uhr', $day, $month[$mon], $year+1900, $hour, $min);
551 }
552
553 sub short_hr_time ($) {
554 my (undef, $min, $hour, $day, $mon, $year) = localtime ($_[0]);
555
556 sprintf ('%02d. %02d. %04d, %02d:%02d Uhr', $day, $mon+1, $year+1900, $hour, $min);
557 }
558
559 sub long_hr_time ($) {
560 my @month = (qw(Januar Februar), "M\303\244rz", qw(April Mail Juni Juli August September Oktober November Dezember));
561 # ^^^^^^^^ - UTF8 #
562
563 my @wday = qw(Sonntag Montag Dienstag Mittwoch Donnerstag Freitag Samstag);
564 my ($sek, $min, $hour, $day, $mon, $year, $wday) = localtime ($_[0]);
565
566 sprintf ('%s, %02d. %s %04d, %02d:%02d:%02d Uhr', $wday[$wday], $day, $month[$mon], $year+1900, $hour, $min, $sek);
567 }
568
569 # ====================================================
570 # Modulinitialisierung
571 # ====================================================
572
573 # making require happy
574 1;

patrick-canterino.de