Annotation of loncom/publisher/loncleanup.pm, revision 1.22
1.1 www 1: # The LearningOnline Network with CAPA
2: # Handler to cleanup XML files
3: #
1.22 ! raeburn 4: # $Id: loncleanup.pm,v 1.21 2023/07/23 13:07:24 raeburn Exp $
1.1 www 5: #
6: # Copyright Michigan State University Board of Trustees
7: #
8: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
9: #
10: # LON-CAPA is free software; you can redistribute it and/or modify
11: # it under the terms of the GNU General Public License as published by
12: # the Free Software Foundation; either version 2 of the License, or
13: # (at your option) any later version.
14: #
15: # LON-CAPA is distributed in the hope that it will be useful,
16: # but WITHOUT ANY WARRANTY; without even the implied warranty of
17: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18: # GNU General Public License for more details.
19: #
20: # You should have received a copy of the GNU General Public License
21: # along with LON-CAPA; if not, write to the Free Software
22: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23: #
24: # /home/httpd/html/adm/gpl.txt
25: #
26: # http://www.lon-capa.org/
27: #
28: #
29: ###
30:
31: package Apache::loncleanup;
32:
33: use strict;
34: use Apache::File;
35: use File::Copy;
36: use Apache::Constants qw(:common :http :methods);
37: use Apache::loncommon();
1.11 bisitz 38: use Apache::lonhtmlcommon();
1.1 www 39: use Apache::lonlocal;
40: use Apache::lonnet;
1.8 www 41: use lib '/home/httpd/lib/perl/';
42: use LONCAPA;
1.19 raeburn 43: use HTML::Entities();
1.8 www 44:
1.1 www 45:
46: sub latextrans {
47: my $symbolfont=shift;
48: my %latexsymb=(
49: '±' => '\pm',
50: '´' => '\times',
51: '¸' => '\div',
52: 'Ò' => '(R)',
53: 'Ó' => '\copy',
54: 'Ø' => '\neg',
55: 'â' => '(R)',
56: 'ã' => '\copy',
57: '¦' => 'f',
58: 'A' => '\Alpha',
59: 'B' => '\Beta',
60: 'G' => '\Gamma',
61: 'D' => '\Delta',
62: 'E' => '\Epsilon',
63: 'Z' => '\Zeta',
64: 'H' => '\Eta',
65: 'Q' => '\Theta',
66: 'I' => '\Iota',
67: 'K' => '\Kappa',
68: 'L' => '\Lambda',
69: 'M' => '\Mu',
70: 'N' => '\Nu',
71: 'X' => '\Xi',
72: 'O' => '\Omicron',
73: 'P' => '\Pi',
74: 'R' => '\Rho',
75: 'S' => '\Sigma',
76: 'T' => '\Tau',
77: 'U' => 'Y',
78: 'F' => '\Phi',
79: 'C' => '\Chi',
80: 'Y' => '\Psi',
81: 'W' => '\Omega',
82: 'a' => '\alpha',
83: 'b' => '\beta',
84: 'g' => '\gamma',
85: 'd' => '\delta',
86: 'e' => '\epsilon',
87: 'z' => '\zeta',
88: 'h' => '\eta',
89: 'q' => '\theta',
90: 'i' => '\iota',
91: 'k' => '\kappa',
92: 'l' => '\lambda',
93: 'm' => '\mu',
94: 'n' => '\nu',
95: 'x' => '\xi',
96: 'o' => '\omicron',
97: 'p' => '\pi',
98: 'r' => '\rho',
99: 'V' => '\sigmaf',
100: 's' => '\sigma',
101: 't' => '\tau',
102: 'u' => '\upsilon',
103: 'f' => '\phi',
104: 'c' => '\chi',
105: 'y' => '\psi',
106: 'w' => '\omega',
107: 'J' => '\vartheta',
108: 'j' => '\varphi',
109: 'v' => '\varpi',
110: '¡' => '\Upsilon',
111: '¢' => "'",
112: '¤' => '/',
113: '²' => '"',
114: '¼' => '\ldots',
115: 'À' => '\aleph',
116: 'Á' => '\Im',
117: 'Â' => '\Re',
118: 'Ã' => '\wp',
119: 'Ô' => '^{TM}',
120: 'ä' => '^{TM}',
121: 'ð' => 'EUR',
122: '«' => '\leftrightarrow',
123: '¬' => '\leftarrow',
124: '­' => '\uparrow',
125: '®' => '\rightarrow',
126: '¯' => '\downarraw',
127: '¿' => '\hookleftarrow',
128: 'Û' => '\Leftrightarrow',
129: 'Ü' => '\Leftarrow',
130: 'Ý' => '\Uparrow',
131: 'Þ' => '\Rightarrow',
132: 'ß' => '\Downarrow',
133: '"' => '\forall',
134: '$' => '\exists',
135: ''' => '\ni',
136: '*' => '\ast',
137: '-' => '-',
138: '@' => '\cong',
139: '\' => '\therefore',
140: '^' => '\perp',
141: '~' => '\sim',
142: '£' => '\leq',
143: '¥' => '\infty',
144: '³' => '\geq',
145: 'µ' => '\propto',
146: '¶' => '\partial',
147: '·' => '\cdot',
148: '¹' => '\not=',
149: 'º' => '\equiv',
150: '»' => '\approx',
151: 'Ä' => '\otimes',
152: 'Å' => '\oplus',
153: 'Æ' => '\emptyset',
154: 'Ç' => '\cap',
155: 'È' => '\cup',
156: 'É' => '\supset',
157: 'Ê' => '\supseteq',
158: 'Ë' => '\not\subset',
159: 'Ì' => '\subset',
160: 'Í' => '\subseteq',
161: 'Î' => '\in',
162: 'Ï' => '\not\in',
163: 'Ð' => '\angle',
164: 'Ñ' => '\nabla',
165: 'Õ' => '\prod',
166: 'Ö' => '\surd',
167: '×' => '\cdot',
168: 'Ù' => '\wedge',
169: 'Ú' => '\wee',
170: 'å' => '\sum',
171: 'ò' => '\int',
172: 'á' => '\langle',
173: 'ñ' => '\rangle',
174: 'à' => '\diamondsuit',
175: '§' => '\clubsuit',
176: '¨' => '\diamondsuit',
177: '©' => '\heartsuit',
178: 'ª' => '\spadesuit'
179: );
180: my $output='';
181: my $char='';
182: my $entitymode=0;
183: for (my $i=0; $i<length($symbolfont); $i++) {
184: my $newchar=substr($symbolfont,$i,1);
185: $char.=$newchar;
186: if ($newchar eq '&') { $entitymode=1; }
187: if (($entitymode) && ($newchar ne ';')) { next; }
188: my $latex=$latexsymb{$char};
189: if ($latex) {
190: $output.=$latex;
191: } else {
192: $output.=$char;
193: }
194: $char='';
195: $entitymode=0;
196: }
197: return $output;
198: }
199:
200: sub insidetrans {
201: my @args=@_;
202: return '<font'.$args[0].$args[1].'><m>$'.&latextrans($args[2]).'$</m>';
203: }
204:
205: sub symbolfontreplace {
206: my $text=shift;
207: my @fragments=split(/\<\/font\>/si,$text);
208: for (my $i=0; $i<=$#fragments;$i++) {
209: $fragments[$i]=~s/\<font([^\>]*)\s+face=[\"\']*symbol[\"\']*([^\>]*)\>(.*)$/&insidetrans($1,$2,$3)/gsie;
210: }
211: return join('</font>',@fragments);
212: }
213:
214: sub htmlclean {
1.2 www 215: my ($raw,$full,$blocklinefeed,$blockemptytags,$blocklowercasing,$blockdesymboling)=@_;
1.1 www 216: # Take care of CRLF etc
1.2 www 217: unless ($blocklinefeed) {
218: $raw=~s/\r\f/\n/gs; $raw=~s/\f\r/\n/gs;
219: $raw=~s/\r\n/\n/gs; $raw=~s/\n\r/\n/gs;
220: $raw=~s/\f/\n/gs; $raw=~s/\r/\n/gs;
221: $raw=~s/\&\#10\;/\n/gs; $raw=~s/\&\#13\;/\n/gs;
222: }
1.1 www 223: # Generate empty tags, remove wrong end tags
1.2 www 224: unless ($blockemptytags) {
1.6 www 225: $raw=~s/\<(br|hr|img|meta|embed|allow|basefont)([^\>]*?)\>/\<$1$2 \/\>/gis;
226: $raw=~s/\<\/(br|hr|img|meta|embed|allow|basefont)\>//gis;
227: $raw=~s/\/ \/\>/\/\>/gs;
1.2 www 228: unless ($full) {
229: $raw=~s/\<[\/]*(body|head|html)\>//gis;
230: }
1.1 www 231: }
232: # Make standard tags lowercase
1.2 www 233: unless ($blocklowercasing) {
234: foreach ('html','body','head','meta','h1','h2','h3','h4','b','i','m',
235: 'table','tr','td','th','p','br','hr','img','embed','font',
236: 'a','strong','center','title','basefont','li','ol','ul',
237: 'input','select','form','option','script','pre') {
238: $raw=~s/\<$_\s*\>/\<$_\>/gis;
239: $raw=~s/\<\/$_\s*\>/<\/$_\>/gis;
240: $raw=~s/\<$_\s([^\>]*)\>/<$_ $1\>/gis;
241: }
242: }
243: # Replace <font face="symbol">
244: unless ($blockdesymboling) {
245: $raw=&symbolfontreplace($raw);
1.1 www 246: }
247: return $raw;
248: }
249:
250: sub phaseone {
1.2 www 251: my ($r,$fn,$uname,$udom)=@_;
1.11 bisitz 252: $r->print(
253: &Apache::lonhtmlcommon::start_pick_box()
254: .&Apache::lonhtmlcommon::row_title(&mt('Select actions to attempt'))
1.18 bisitz 255: .'<label>'
1.11 bisitz 256: .'<input type="checkbox" name="linefeed" checked="checked" /> '
257: .&mt('Linefeeds, formfeeds, and carriage returns')
1.18 bisitz 258: .'</label><br />'
259: .'<label>'
1.11 bisitz 260: .'<input type="checkbox" name="empty" checked="checked" /> '
261: .&mt('Empty tags')
1.18 bisitz 262: .'</label><br />'
263: .'<label>'
1.11 bisitz 264: .'<input type="checkbox" name="lower" checked="checked" /> '
265: .&mt('Lower casing')
1.18 bisitz 266: .'</label><br />'
267: .'<label>'
1.11 bisitz 268: .'<input type="checkbox" name="symbol" checked="checked" /> '
269: .&mt('Symbol font')
1.18 bisitz 270: .'</label>'
1.11 bisitz 271: .&Apache::lonhtmlcommon::row_closure(1)
272: .&Apache::lonhtmlcommon::end_pick_box()
273: );
274:
275: $r->print(
276: '<input type="hidden" name="phase" value="two" />'
277: .'<p>'
278: .'<input type="submit" value="'.&mt('Next').'" />'
279: .'</p>'
280: );
1.1 www 281: }
282:
283: sub phasetwo {
1.11 bisitz 284: # Check original file
1.2 www 285: my ($r,$fn,$uname,$udom)=@_;
1.4 www 286: my $text='';
1.14 raeburn 287: my $londocroot = $r->dir_config('lonDocRoot');
288: if (open(IN,"<$londocroot/priv/$udom/$uname".$fn)) {
289: while (my $line=<IN>) {
290: $text.=$line;
291: }
292: close(IN);
1.4 www 293: }
1.18 bisitz 294: # Check if any selection was made
295: if ($env{'form.linefeed'} ne 'on' &&
296: $env{'form.empty'} ne 'on' &&
297: $env{'form.lower'} ne 'on' &&
298: $env{'form.symbol'} ne 'on') {
299: $r->print(
300: '<p class="LC_warning">'
301: .&mt('Please select at least one option.')
302: .'</p>'
303: .'<p><a href="javascript:history.back();">'.&mt('Back').'</p>'
304: );
305: return;
306: }
307:
1.14 raeburn 308: my $uri="/priv/$udom/$uname".$fn;
1.4 www 309: my $result=&Apache::lonnet::ssi_body($uri,
310: ('grade_target'=>'web',
311: 'return_only_error_and_warning_counts' => 1));
312: my ($errorcount,$warningcount)=split(':',$result);
1.11 bisitz 313:
314: # Display results for original file
315: $r->print(
316: &Apache::lonhtmlcommon::start_pick_box()
317: .&Apache::lonhtmlcommon::row_title(&mt('Original file'))
318: .&Apache::lonhtmlcommon::confirm_success(
319: &mt('[quant,_1,error]',$errorcount), $errorcount)
320: .'<br />'
321: .&Apache::lonhtmlcommon::confirm_success(
322: &mt('[quant,_1,warning]',$warningcount), $warningcount)
323: .&Apache::lonhtmlcommon::row_closure()
324: );
325:
326: # Clean up file
1.4 www 327: $text=&htmlclean($text,1,
328: ($env{'form.linefeed'} ne 'on'),
329: ($env{'form.empty'} ne 'on'),
330: ($env{'form.lower'} ne 'on'),
331: ($env{'form.symbol'} ne 'on'));
332: my ($main,$ext)=($fn=~/^(.*)\.(\w+)/);
333: my $newfn=$main.'_Auto_Cleaned_Up.'.$ext;
1.14 raeburn 334: if (open(OUT,">$londocroot/priv/$udom/$uname".$newfn)) {
335: print OUT $text;
336: close(OUT);
337: }
338: my $newuri="/priv/$udom/$uname".$newfn;
1.4 www 339: $result=&Apache::lonnet::ssi_body($newuri,
1.11 bisitz 340: ('grade_target'=>'web',
341: 'return_only_error_and_warning_counts' => 1));
1.4 www 342: ($errorcount,$warningcount)=split(':',$result);
1.11 bisitz 343:
344: # Display results for cleaned up file
345: $r->print(
346: &Apache::lonhtmlcommon::row_title(&mt('Cleaned up file'))
347: .&Apache::lonhtmlcommon::confirm_success(
348: &mt('[quant,_1,error]',$errorcount), $errorcount)
349: .'<br />'
350: .&Apache::lonhtmlcommon::confirm_success(
351: &mt('[quant,_1,warning]',$warningcount), $warningcount)
352: .&Apache::lonhtmlcommon::row_closure()
353: );
354:
355: # Display actions
356: $r->print(
357: &Apache::lonhtmlcommon::row_title(&mt('Actions'))
358: .'<ul>'
359: .'<li><a href="'.$newuri.'" target="prev">'
360: .&mt('Open (and edit) cleaned up file in new window')
361: .'</a></li>'
1.18 bisitz 362: .'<li><a href="'
363: .&HTML::Entities::encode(
364: '/adm/diff?filename='.&escape($uri)
365: .'&versionone=priv&filetwo='.&escape($newuri))
366: .'" target="prev">'
1.11 bisitz 367: .&mt('Show diffs in new window')
368: .'</a></li>'
369: .'</ul>'
370: .&Apache::lonhtmlcommon::row_closure(1)
371: .&Apache::lonhtmlcommon::end_pick_box()
372: .'<p>'
373: .'<input type="hidden" name="phase" value="three" />'
374: .'<input type="submit" name="accept" value="'
375: .&mt('Clean Up').'" />'
376: .' <input type="submit" name="reject" value="'
377: .&mt('Cancel').'" />'
378: .'</p>'
379: );
1.2 www 380: }
381:
382: sub phasethree {
383: my ($r,$fn,$uname,$udom)=@_;
1.14 raeburn 384: my $old=$r->dir_config('lonDocRoot')."/priv/$udom/$uname".$fn;
1.4 www 385: my ($main,$ext)=($fn=~/^(.*)\.(\w+)/);
386: my $newfn=$main.'_Auto_Cleaned_Up.'.$ext;
1.14 raeburn 387: my $new=$r->dir_config('lonDocRoot')."/priv/$udom/$uname".$newfn;
1.4 www 388: if ($env{'form.accept'}) {
1.18 bisitz 389: $r->print(
1.11 bisitz 390: '<p class="LC_info">'
1.18 bisitz 391: .&mt('Accepting changes...')
1.11 bisitz 392: .'</p>'
393: );
1.4 www 394: move($new,$old);
395: } else {
1.18 bisitz 396: $r->print(
1.11 bisitz 397: '<p class="LC_info">'
1.18 bisitz 398: .&mt('Rejecting changes...')
1.11 bisitz 399: .'</p>'
1.18 bisitz 400: );
1.4 www 401: unlink($new);
402: }
1.18 bisitz 403: $r->print(
404: '<p>'
405: .&Apache::lonhtmlcommon::confirm_success(&mt('Done')));
406: '</p>'
1.1 www 407: }
408:
409: # ---------------------------------------------------------------- Main Handler
410: sub handler {
411:
1.3 albertel 412: my $r=shift;
1.4 www 413: my $fn='';
1.1 www 414:
415: # Get query string for limited number of parameters
416:
1.3 albertel 417: &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},
418: ['filename']);
1.1 www 419:
1.3 albertel 420: if ($env{'form.filename'}) {
421: $fn=$env{'form.filename'};
1.15 raeburn 422: $fn=~s{^https?\://[^/]+}{};
1.3 albertel 423: } else {
424: $r->log_reason($env{'user.name'}.' at '.$env{'user.domain'}.
425: ' unspecified filename for cleanup', $r->filename);
426: return HTTP_NOT_FOUND;
427: }
428:
429: unless ($fn) {
430: $r->log_reason($env{'user.name'}.' at '.$env{'user.domain'}.
431: ' trying to cleanup non-existing file', $r->filename);
432: return HTTP_NOT_FOUND;
433: }
1.1 www 434:
435: # ----------------------------------------------------------- Start page output
1.3 albertel 436: my $uname;
437: my $udom;
438:
1.17 raeburn 439: ($uname,$udom)=&Apache::lonnet::constructaccess($fn);
1.3 albertel 440: unless (($uname) && ($udom)) {
441: $r->log_reason($uname.' at '.$udom.
442: ' trying to cleanup file '.$env{'form.filename'}.
443: ' ('.$fn.') - not authorized',
444: $r->filename);
445: return HTTP_NOT_ACCEPTABLE;
446: }
447:
448: &Apache::loncommon::content_type($r,'text/html');
449: $r->send_http_header;
450:
1.11 bisitz 451: # Breadcrumbs
1.21 raeburn 452: my $text = 'Authoring Space';
453: my $href = &Apache::loncommon::authorspace($fn);
454: if ($env{'request.course.id'}) {
455: my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
456: my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
457: if ($href eq "/priv/$cdom/$cnum/") {
458: $text = 'Course Authoring Space';
459: }
460: }
461: my $brcrum = [{'href' => $href,
1.22 ! raeburn 462: 'text' => $text},
1.11 bisitz 463: {'href' => '',
464: 'text' => 'Cleanup XML Document'}];
465:
1.16 raeburn 466: $fn=~s{^/priv/$LONCAPA::domain_re/$LONCAPA::username_re}{};
467:
1.11 bisitz 468: $r->print(&Apache::loncommon::start_page('Cleanup XML Document',
469: undef,
470: {'bread_crumbs' => $brcrum,}));
1.4 www 471: $r->print('<h2>'.$fn.'</h2>'.
472: '<form action="/adm/cleanup" method="post">'.
473: '<input type="hidden" name="filename" value="'.$env{'form.filename'}.'" />');
474: unless ($fn=~/\.(problem|exam|quiz|assess|survey|form|library|xml|html|htm|xhtml|xhtm|sty)$/) {
1.18 bisitz 475: $r->print('<p class="LC_warning">'.&mt('Cannot cleanup this filetype').'</p>');
1.3 albertel 476: } else {
1.4 www 477: if ($env{'form.phase'} eq 'three') {
478: &phasethree($r,$fn,$uname,$udom);
479: } elsif ($env{'form.phase'} eq 'two') {
480: &phasetwo($r,$fn,$uname,$udom);
481: } else {
482: &phaseone($r,$fn,$uname,$udom);
483: }
1.3 albertel 484: }
1.4 www 485: my $dir=$fn;
1.14 raeburn 486: $dir=~s{[^/]+$}{};
1.11 bisitz 487: $r->print(
488: '</form>'
1.18 bisitz 489: .&Apache::lonhtmlcommon::actionbox(
490: ['<a href="/priv/'.$udom.'/'.$uname.$fn.'">'.
491: &mt('Back to Source File').'</a>',
492: '<a href="/priv/'.$udom.'/'.$uname.$dir.'">'.
493: &mt('Back to Source Directory').'</a>'])
1.11 bisitz 494: .&Apache::loncommon::end_page()
495: );
496:
1.3 albertel 497: return OK;
1.1 www 498: }
499:
500: 1;
501: __END__
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>