Subversion Repositories planix.SVN

Rev

Rev 2 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 - 1
#include <u.h>
2
#include <libc.h>
3
#include <bio.h>
4
#include <String.h>
5
#include <thread.h>
6
#include "wiki.h"
7
 
8
/*
9
 * Get HTML and text templates from underlying file system.
10
 * Caches them, which means changes don't take effect for
11
 * up to Tcache seconds after they are made.
12
 * 
13
 * If the files are deleted, we keep returning the last
14
 * known copy.
15
 */
16
enum {
17
	WAIT = 60
18
};
19
 
20
static char *name[2*Ntemplate] = {
21
 [Tpage]		"page.html",
22
 [Tedit]		"edit.html",
23
 [Tdiff]		"diff.html",
24
 [Thistory]		"history.html",
25
 [Tnew]		"new.html",
26
 [Toldpage]	"oldpage.html",
27
 [Twerror]		"werror.html",
28
 [Ntemplate+Tpage]	"page.txt",
29
 [Ntemplate+Tdiff]	"diff.txt",
30
 [Ntemplate+Thistory]	"history.txt",
31
 [Ntemplate+Toldpage]	"oldpage.txt",
32
 [Ntemplate+Twerror]	"werror.txt",
33
};
34
 
35
static struct {
36
	RWLock;
37
	String *s;
38
	ulong t;
39
	Qid qid;
40
} cache[2*Ntemplate];
41
 
42
static void
43
cacheinit(void)
44
{
45
	int i;
46
	static int x;
47
	static Lock l;
48
 
49
	if(x)
50
		return;
51
	lock(&l);
52
	if(x){
53
		unlock(&l);
54
		return;
55
	}
56
 
57
	for(i=0; i<2*Ntemplate; i++)
58
		if(name[i])
59
			cache[i].s = s_copy("");
60
	x = 1;
61
	unlock(&l);
62
}
63
 
64
static String*
65
gettemplate(int type)
66
{
67
	int n;
68
	Biobuf *b;
69
	Dir *d;
70
	String *s, *ns;
71
 
72
	if(name[type]==nil)
73
		return nil;
74
 
75
	cacheinit();
76
 
77
	rlock(&cache[type]);
78
	if(0 && cache[type].t+Tcache >= time(0)){
79
		s = s_incref(cache[type].s);
80
		runlock(&cache[type]);
81
		return s;
82
	}
83
	runlock(&cache[type]);
84
 
85
//	d = nil;
86
	wlock(&cache[type]);
87
	if(0 && cache[type].t+Tcache >= time(0) || (d = wdirstat(name[type])) == nil)
88
		goto Return;
89
 
90
	if(0 && d->qid.vers == cache[type].qid.vers && d->qid.path == cache[type].qid.path){
91
		cache[type].t = time(0);
92
		goto Return;
93
	}
94
 
95
	if((b = wBopen(name[type], OREAD)) == nil)
96
		goto Return;
97
 
98
	ns = s_reset(nil);
99
	do
100
		n = s_read(b, ns, Bsize);
101
	while(n > 0);
102
	Bterm(b);
103
	if(n < 0) {
104
		s_free(ns);
105
		goto Return;
106
	}
107
 
108
	s_free(cache[type].s);
109
	cache[type].s = ns;
110
	cache[type].qid = d->qid;
111
	cache[type].t = time(0);
112
 
113
Return:
114
	free(d);
115
	s = s_incref(cache[type].s);
116
	wunlock(&cache[type]);
117
	return s;
118
}
119
 
120
 
121
/*
122
 * Write wiki document in HTML.
123
 */
124
static String*
125
s_escappend(String *s, char *p, int pre)
126
{
127
	char *q;
128
 
129
	while(q = strpbrk(p, pre ? "<>&" : " <>&")){
130
		s = s_nappend(s, p, q-p);
131
		switch(*q){
132
		case '<':
133
			s = s_append(s, "&lt;");
134
			break;
135
		case '>':
136
			s = s_append(s, "&gt;");
137
			break;
138
		case '&':
139
			s = s_append(s, "&amp;");
140
			break;
141
		case ' ':
142
			s = s_append(s, "\n");
143
		}
144
		p = q+1;
145
	}
146
	s = s_append(s, p);
147
	return s;
148
}
149
 
150
static char*
151
mkurl(char *s, int ty)
152
{
153
	char *p, *q;
154
 
155
	if(strncmp(s, "http:", 5)==0
156
	|| strncmp(s, "https:", 6)==0
157
	|| strncmp(s, "#", 1)==0
158
	|| strncmp(s, "ftp:", 4)==0
159
	|| strncmp(s, "mailto:", 7)==0
160
	|| strncmp(s, "telnet:", 7)==0
161
	|| strncmp(s, "file:", 5)==0)
162
		return estrdup(s);
163
 
164
	if(strchr(s, ' ')==nil && strchr(s, '@')!=nil){
165
		p = emalloc(strlen(s)+8);
166
		strcpy(p, "mailto:");
167
		strcat(p, s);
168
		return p;
169
	}
170
 
171
	if(ty == Toldpage)
172
		p = smprint("../../%s", s);
173
	else
174
		p = smprint("../%s", s);
175
 
176
	for(q=p; *q; q++)
177
		if(*q==' ')
178
			*q = '_';
179
	return p;
180
}
181
 
182
int okayinlist[Nwtxt] =
183
{
184
	[Wbullet]	1,
185
	[Wlink]	1,
186
	[Wman]	1,
187
	[Wplain]	1,
188
};
189
 
190
int okayinpre[Nwtxt] =
191
{
192
	[Wlink]	1,
193
	[Wman]	1,
194
	[Wpre]	1,
195
};
196
 
197
int okayinpara[Nwtxt] =
198
{
199
	[Wpara]	1,
200
	[Wlink]	1,
201
	[Wman]	1,
202
	[Wplain]	1,
203
};
204
 
205
char*
206
nospaces(char *s)
207
{
208
	char *q;
209
	s = strdup(s);
210
	if(s == nil)
211
		return nil;
212
	for(q=s; *q; q++)
213
		if(*q == ' ')
214
			*q = '_';
215
	return s;
216
}
217
 
218
String*
219
pagehtml(String *s, Wpage *wtxt, int ty)
220
{
221
	char *p, tmp[40];
222
	int inlist, inpara, inpre, t, tnext;
223
	Wpage *w;
224
 
225
	inlist = 0;
226
	inpre = 0;
227
	inpara = 0;
228
 
229
	for(w=wtxt; w; w=w->next){
230
		t = w->type;
231
		tnext = Whr;
232
		if(w->next)
233
			tnext = w->next->type;
234
 
235
		if(inlist && !okayinlist[t]){
236
			inlist = 0;
237
			s = s_append(s, "\n</li>\n</ul>\n");
238
		}
239
		if(inpre && !okayinpre[t]){
240
			inpre = 0;
241
			s = s_append(s, "</pre>\n");
242
		}
243
 
244
		switch(t){
245
		case Wheading:
246
			p = nospaces(w->text);
247
			s = s_appendlist(s, 
248
				"\n<a name=\"", p, "\" /><h3>", 
249
				w->text, "</h3>\n", nil);
250
			free(p);
251
			break;
252
 
253
		case Wpara:
254
			if(inpara){
255
				s = s_append(s, "\n</p>\n");
256
				inpara = 0;
257
			}
258
			if(okayinpara[tnext]){
259
				s = s_append(s, "\n<p class='para'>\n");
260
				inpara = 1;
261
			}
262
			break;
263
 
264
		case Wbullet:
265
			if(!inlist){
266
				inlist = 1;
267
				s = s_append(s, "\n<ul>\n");
268
			}else
269
				s = s_append(s, "\n</li>\n");
270
			s = s_append(s, "\n<li>\n");
271
			break;
272
 
273
		case Wlink:
274
			if(w->url == nil)
275
				p = mkurl(w->text, ty);
276
			else
277
				p = w->url;
278
			s = s_appendlist(s, "<a href=\"", p, "\">", nil);
279
			s = s_escappend(s, w->text, 0);
280
			s = s_append(s, "</a>");
281
			if(w->url == nil)
282
				free(p);
283
			break;
284
 
285
		case Wman:
286
			sprint(tmp, "%d", w->section);
287
			s = s_appendlist(s, 
288
				"<a href=\"http://plan9.bell-labs.com/magic/man2html/",
289
				tmp, "/", w->text, "\"><i>", w->text, "</i>(",
290
				tmp, ")</a>", nil);
291
			break;
292
 
293
		case Wpre:
294
			if(!inpre){
295
				inpre = 1;
296
				s = s_append(s, "\n<pre>\n");
297
			}
298
			s = s_escappend(s, w->text, 1);
299
			s = s_append(s, "\n");
300
			break;
301
 
302
		case Whr:
303
			s = s_append(s, "<hr />");
304
			break;
305
 
306
		case Wplain:
307
			s = s_escappend(s, w->text, 0);
308
			break;
309
		}
310
	}
311
	if(inlist)
312
		s = s_append(s, "\n</li>\n</ul>\n");
313
	if(inpre)
314
		s = s_append(s, "</pre>\n");
315
	if(inpara)
316
		s = s_append(s, "\n</p>\n");
317
	return s;
318
}
319
 
320
static String*
321
copythru(String *s, char **newp, int *nlinep, int l)
322
{
323
	char *oq, *q, *r;
324
	int ol;
325
 
326
	q = *newp;
327
	oq = q;
328
	ol = *nlinep;
329
	while(ol < l){
330
		if(r = strchr(q, '\n'))
331
			q = r+1;
332
		else{
333
			q += strlen(q);
334
			break;
335
		}
336
		ol++;
337
	}
338
	if(*nlinep < l)
339
		*nlinep = l;
340
	*newp = q;
341
	return s_nappend(s, oq, q-oq);
342
}
343
 
344
static int
345
dodiff(char *f1, char *f2)
346
{
347
	int p[2];
348
 
349
	if(pipe(p) < 0){
350
		return -1;
351
	}
352
 
353
	switch(fork()){
354
	case -1:
355
		return -1;
356
 
357
	case 0:
358
		close(p[0]);
359
		dup(p[1], 1);
360
		execl("/bin/diff", "diff", f1, f2, nil);
361
		_exits(nil);
362
	}
363
	close(p[1]);
364
	return p[0];
365
}
366
 
367
 
368
/* print document i grayed out, with only diffs relative to j in black */
369
static String*
370
s_diff(String *s, Whist *h, int i, int j)
371
{
372
	char *p, *q, *pnew;
373
	int fdiff, fd1, fd2, n1, n2;
374
	Biobuf b;
375
	char fn1[40], fn2[40];
376
	String *new, *old;
377
	int nline;
378
 
379
	if(j < 0)
380
		return pagehtml(s, h->doc[i].wtxt, Tpage);
381
 
382
	strcpy(fn1, "/tmp/wiki.XXXXXX");
383
	strcpy(fn2, "/tmp/wiki.XXXXXX");
384
	if((fd1 = opentemp(fn1)) < 0 || (fd2 = opentemp(fn2)) < 0){
385
		close(fd1);
386
		s = s_append(s, "\nopentemp failed; sorry\n");
387
		return s;
388
	}
389
 
390
	new = pagehtml(s_reset(nil), h->doc[i].wtxt, Tpage);
391
	old = pagehtml(s_reset(nil), h->doc[j].wtxt, Tpage);
392
	write(fd1, s_to_c(new), s_len(new));
393
	write(fd2, s_to_c(old), s_len(old));
394
 
395
	fdiff = dodiff(fn2, fn1);
396
	if(fdiff < 0)
397
		s = s_append(s, "\ndiff failed; sorry\n");
398
	else{
399
		nline = 0;
400
		pnew = s_to_c(new);
401
		Binit(&b, fdiff, OREAD);
402
		while(p = Brdline(&b, '\n')){
403
			if(p[0]=='<' || p[0]=='>' || p[0]=='-')
404
				continue;
405
			p[Blinelen(&b)-1] = '\0';
406
			if((p = strpbrk(p, "acd")) == nil)
407
				continue;
408
			n1 = atoi(p+1);
409
			if(q = strchr(p, ','))
410
				n2 = atoi(q+1);
411
			else
412
				n2 = n1;
413
			switch(*p){
414
			case 'a':
415
			case 'c':
416
				s = s_append(s, "<span class='old_text'>");
417
				s = copythru(s, &pnew, &nline, n1-1);
418
				s = s_append(s, "</span><span class='new_text'>");
419
				s = copythru(s, &pnew, &nline, n2);
420
				s = s_append(s, "</span>");
421
				break;
422
			}
423
		}
424
		close(fdiff);
425
		s = s_append(s, "<span class='old_text'>");
426
		s = s_append(s, pnew);
427
		s = s_append(s, "</span>");
428
 
429
	}
430
	s_free(new);
431
	s_free(old);
432
	close(fd1);
433
	close(fd2);
434
	return s;
435
}
436
 
437
static String*
438
diffhtml(String *s, Whist *h)
439
{
440
	int i;
441
	char tmp[50];
442
	char *atime;
443
 
444
	for(i=h->ndoc-1; i>=0; i--){
445
		s = s_append(s, "<hr /><div class='diff_head'>\n");
446
		if(i==h->current)
447
			sprint(tmp, "index.html");
448
		else
449
			sprint(tmp, "%lud", h->doc[i].time);
450
		atime = ctime(h->doc[i].time);
451
		atime[strlen(atime)-1] = '\0';
452
		s = s_appendlist(s, 
453
			"<a href=\"", tmp, "\">",
454
			atime, "</a>", nil);
455
		if(h->doc[i].author)
456
			s = s_appendlist(s, ", ", h->doc[i].author, nil);
457
		if(h->doc[i].conflict)
458
			s = s_append(s, ", conflicting write");
459
		s = s_append(s, "\n");
460
		if(h->doc[i].comment)
461
			s = s_appendlist(s, "<br /><i>", h->doc[i].comment, "</i>\n", nil);
462
		s = s_append(s, "</div><hr />");
463
		s = s_diff(s, h, i, i-1);
464
	}
465
	s = s_append(s, "<hr>");
466
	return s;
467
}
468
 
469
static String*
470
historyhtml(String *s, Whist *h)
471
{
472
	int i;
473
	char tmp[40];
474
	char *atime;
475
 
476
	s = s_append(s, "<ul>\n");
477
	for(i=h->ndoc-1; i>=0; i--){
478
		if(i==h->current)
479
			sprint(tmp, "index.html");
480
		else
481
			sprint(tmp, "%lud", h->doc[i].time);
482
		atime = ctime(h->doc[i].time);
483
		atime[strlen(atime)-1] = '\0';
484
		s = s_appendlist(s, 
485
			"<li><a href=\"", tmp, "\">",
486
			atime, "</a>", nil);
487
		if(h->doc[i].author)
488
			s = s_appendlist(s, ", ", h->doc[i].author, nil);
489
		if(h->doc[i].conflict)
490
			s = s_append(s, ", conflicting write");
491
		s = s_append(s, "\n");
492
		if(h->doc[i].comment)
493
			s = s_appendlist(s, "<br><i>", h->doc[i].comment, "</i>\n", nil);
494
	}
495
	s = s_append(s, "</ul>");
496
	return s;		
497
}
498
 
499
String*
500
tohtml(Whist *h, Wdoc *d, int ty)
501
{
502
	char *atime;
503
	char *p, *q, ver[40];
504
	int nsub;
505
	Sub sub[3];
506
	String *s, *t;
507
 
508
	t = gettemplate(ty);
509
	if(p = strstr(s_to_c(t), "PAGE"))
510
		q = p+4;
511
	else{
512
		p = s_to_c(t)+s_len(t);
513
		q = nil;
514
	}
515
 
516
	nsub = 0;
517
	if(h){
518
		sub[nsub] = (Sub){ "TITLE", h->title };
519
		nsub++;
520
	}
521
	if(d){
522
		sprint(ver, "%lud", d->time);
523
		sub[nsub] = (Sub){ "VERSION", ver };
524
		nsub++;
525
		atime = ctime(d->time);
526
		atime[strlen(atime)-1] = '\0';
527
		sub[nsub] = (Sub){ "DATE", atime };
528
		nsub++;
529
	}
530
 
531
	s = s_reset(nil);
532
	s = s_appendsub(s, s_to_c(t), p-s_to_c(t), sub, nsub);
533
	switch(ty){
534
	case Tpage:
535
	case Toldpage:
536
		s = pagehtml(s, d->wtxt, ty);
537
		break;
538
	case Tedit:
539
		s = pagetext(s, d->wtxt, 0);
540
		break;
541
	case Tdiff:
542
		s = diffhtml(s, h);
543
		break;
544
	case Thistory:
545
		s = historyhtml(s, h);
546
		break;
547
	case Tnew:
548
	case Twerror:
549
		break;
550
	}
551
	if(q)
552
		s = s_appendsub(s, q, strlen(q), sub, nsub);
553
	s_free(t);
554
	return s;
555
}
556
 
557
enum {
558
	LINELEN = 70,
559
};
560
 
561
static String*
562
s_appendbrk(String *s, char *p, char *prefix, int dosharp)
563
{
564
	char *e, *w, *x;
565
	int first, l;
566
	Rune r;
567
 
568
	first = 1;
569
	while(*p){
570
		s = s_append(s, p);
571
		e = strrchr(s_to_c(s), '\n');
572
		if(e == nil)
573
			e = s_to_c(s);
574
		else
575
			e++;
576
		if(utflen(e) <= LINELEN)
577
			break;
578
		x = e; l=LINELEN;
579
		while(l--)
580
			x+=chartorune(&r, x);
581
		x = strchr(x, ' ');
582
		if(x){
583
			*x = '\0';
584
			w = strrchr(e, ' ');
585
			*x = ' ';
586
		}else
587
			w = strrchr(e, ' ');
588
 
589
		if(w-s_to_c(s) < strlen(prefix))
590
			break;
591
 
592
		x = estrdup(w+1);
593
		*w = '\0';
594
		s->ptr = w;
595
		s_append(s, "\n");
596
		if(dosharp)
597
			s_append(s, "#");
598
		s_append(s, prefix);
599
		if(!first)
600
			free(p);
601
		first = 0;
602
		p = x;
603
	}
604
	if(!first)
605
		free(p);
606
	return s;
607
}
608
 
609
static void
610
s_endline(String *s, int dosharp)
611
{
612
	if(dosharp){
613
		if(s->ptr == s->base+1 && s->ptr[-1] == '#')
614
			return;
615
 
616
		if(s->ptr > s->base+1 && s->ptr[-1] == '#' && s->ptr[-2] == '\n')
617
			return;
618
		s_append(s, "\n#");
619
	}else{
620
		if(s->ptr > s->base+1 && s->ptr[-1] == '\n')
621
			return;
622
		s_append(s, "\n");
623
	}
624
}
625
 
626
String*
627
pagetext(String *s, Wpage *page, int dosharp)
628
{
629
	int inlist, inpara;
630
	char *prefix, *sharp, tmp[40];
631
	String *t;
632
	Wpage *w;
633
 
634
	inlist = 0;
635
	inpara = 0;
636
	prefix = "";
637
	sharp = dosharp ? "#" : "";
638
	s = s_append(s, sharp);
639
	for(w=page; w; w=w->next){
640
		switch(w->type){
641
		case Wheading:
642
			if(inlist){
643
				prefix = "";
644
				inlist = 0;
645
			}
646
			s_endline(s, dosharp);
647
			if(!inpara){
648
				inpara = 1;
649
				s = s_appendlist(s, "\n", sharp, nil);
650
			}
651
			s = s_appendlist(s, w->text, "\n", sharp, "\n", sharp, nil);
652
			break;
653
 
654
		case Wpara:
655
			s_endline(s, dosharp);
656
			if(inlist){
657
				prefix = "";
658
				inlist = 0;
659
			}
660
			if(!inpara){
661
				inpara = 1;
662
				s = s_appendlist(s, "\n", sharp, nil);
663
			}
664
			break;
665
 
666
		case Wbullet:
667
			s_endline(s, dosharp);
668
			if(!inlist)
669
				inlist = 1;
670
			if(inpara)
671
				inpara = 0;
672
			s = s_append(s, " *\t");
673
			prefix = "\t";
674
			break;
675
 
676
		case Wlink:
677
			if(inpara)
678
				inpara = 0;
679
			t = s_append(s_copy("["), w->text);
680
			if(w->url == nil)
681
				t = s_append(t, "]");
682
			else{
683
				t = s_append(t, " | ");
684
				t = s_append(t, w->url);
685
				t = s_append(t, "]");
686
			}
687
			s = s_appendbrk(s, s_to_c(t), prefix, dosharp);
688
			s_free(t);
689
			break;
690
 
691
		case Wman:
692
			if(inpara)
693
				inpara = 0;
694
			s = s_appendbrk(s, w->text, prefix, dosharp);
695
			sprint(tmp, "(%d)", w->section);
696
			s = s_appendbrk(s, tmp, prefix, dosharp);
697
			break;
698
 
699
		case Wpre:
700
			if(inlist){
701
				prefix = "";
702
				inlist = 0;
703
			}
704
			if(inpara)
705
				inpara = 0;
706
			s_endline(s, dosharp);
707
			s = s_appendlist(s, "! ", w->text, "\n", sharp, nil);
708
			break;
709
		case Whr:
710
			s_endline(s, dosharp);
711
			s = s_appendlist(s, "------------------------------------------------------ \n", sharp, nil);
712
			break;
713
 
714
		case Wplain:
715
			if(inpara)
716
				inpara = 0;
717
			s = s_appendbrk(s, w->text, prefix, dosharp);
718
			break;
719
		}
720
	}
721
	s_endline(s, dosharp);
722
	s->ptr--;
723
	*s->ptr = '\0';
724
	return s;
725
}
726
 
727
static String*
728
historytext(String *s, Whist *h)
729
{
730
	int i;
731
	char tmp[40];
732
	char *atime;
733
 
734
	for(i=h->ndoc-1; i>=0; i--){
735
		if(i==h->current)
736
			sprint(tmp, "[current]");
737
		else
738
			sprint(tmp, "[%lud/]", h->doc[i].time);
739
		atime = ctime(h->doc[i].time);
740
		atime[strlen(atime)-1] = '\0';
741
		s = s_appendlist(s, " * ", tmp, " ", atime, nil);
742
		if(h->doc[i].author)
743
			s = s_appendlist(s, ", ", h->doc[i].author, nil);
744
		if(h->doc[i].conflict)
745
			s = s_append(s, ", conflicting write");
746
		s = s_append(s, "\n");
747
		if(h->doc[i].comment)
748
			s = s_appendlist(s, "<i>", h->doc[i].comment, "</i>\n", nil);
749
	}
750
	return s;		
751
}
752
 
753
String*
754
totext(Whist *h, Wdoc *d, int ty)
755
{
756
	char *atime;
757
	char *p, *q, ver[40];
758
	int nsub;
759
	Sub sub[3];
760
	String *s, *t;
761
 
762
	t = gettemplate(Ntemplate+ty);
763
	if(p = strstr(s_to_c(t), "PAGE"))
764
		q = p+4;
765
	else{
766
		p = s_to_c(t)+s_len(t);
767
		q = nil;
768
	}
769
 
770
	nsub = 0;
771
	if(h){
772
		sub[nsub] = (Sub){ "TITLE", h->title };
773
		nsub++;
774
	}
775
	if(d){
776
		sprint(ver, "%lud", d->time);
777
		sub[nsub] = (Sub){ "VERSION", ver };
778
		nsub++;
779
		atime = ctime(d->time);
780
		atime[strlen(atime)-1] = '\0';
781
		sub[nsub] = (Sub){ "DATE", atime };
782
		nsub++;
783
	}
784
 
785
	s = s_reset(nil);
786
	s = s_appendsub(s, s_to_c(t), p-s_to_c(t), sub, nsub);
787
	switch(ty){
788
	case Tpage:
789
	case Toldpage:
790
		s = pagetext(s, d->wtxt, 0);
791
		break;
792
	case Thistory:
793
		s = historytext(s, h);
794
		break;
795
	case Tnew:
796
	case Twerror:
797
		break;
798
	}
799
	if(q)
800
		s = s_appendsub(s, q, strlen(q), sub, nsub);
801
	s_free(t);
802
	return s;
803
}
804
 
805
String*
806
doctext(String *s, Wdoc *d)
807
{
808
	char tmp[40];
809
 
810
	sprint(tmp, "D%lud", d->time);
811
	s = s_append(s, tmp);
812
	if(d->comment){
813
		s = s_append(s, "\nC");
814
		s = s_append(s, d->comment);
815
	}
816
	if(d->author){
817
		s = s_append(s, "\nA");
818
		s = s_append(s, d->author);
819
	}
820
	if(d->conflict)
821
		s = s_append(s, "\nX");
822
	s = s_append(s, "\n");
823
	s = pagetext(s, d->wtxt, 1);
824
	return s;
825
}