From http://www.jwz.org/xscreensaver/xscreensaver-5.30.tar.gz
[xscreensaver] / OSX / iostextclient.m
1 /* xscreensaver, Copyright (c) 2012-2014 Jamie Zawinski <jwz@jwz.org>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  *
11  * Loading URLs and returning the underlying text.
12  *
13  * This is necessary because iOS doesn't have Perl installed, so we can't
14  * run "xscreensaver-text" to do this.
15  */
16
17 #include "utils.h"
18
19 #ifdef USE_IPHONE /* whole file -- see utils/textclient.c */
20
21 #include "textclient.h"
22 #include "resources.h"
23
24 #include <stdio.h>
25
26 extern const char *progname;
27
28 struct text_data {
29
30   enum { DATE, LITERAL, URL } mode;
31   char *literal, *url;
32
33   int columns;
34   int max_lines;
35   char *buf;
36   int buf_size;
37   char *fp;
38
39 };
40
41
42 text_data *
43 textclient_open (Display *dpy)
44 {
45   text_data *d = (text_data *) calloc (1, sizeof (*d));
46
47 # ifdef DEBUG
48   fprintf (stderr, "%s: textclient: init\n", progname);
49 # endif
50
51   char *s = get_string_resource (dpy, "textMode", "TextMode");
52   if (!s || !*s || !strcasecmp (s, "date") || !strcmp (s, "0"))
53     d->mode = DATE;
54   else if (!strcasecmp (s, "literal") || !strcmp (s, "1"))
55     d->mode = LITERAL;
56   else if (!strcasecmp (s, "url") || !strcmp (s, "3"))
57     d->mode = URL;
58   else
59     d->mode = DATE;
60
61   d->literal = get_string_resource (dpy, "textLiteral", "TextLiteral");
62   d->url = get_string_resource (dpy, "textURL", "TextURL");
63
64   return d;
65 }
66
67
68 void
69 textclient_close (text_data *d)
70 {
71 # ifdef DEBUG
72   fprintf (stderr, "%s: textclient: free\n", progname);
73 # endif
74
75   if (d->buf) free (d->buf);
76   if (d->literal) free (d->literal);
77   if (d->url) free (d->url);
78   free (d);
79 }
80
81
82 static char *
83 date_string (void)
84 {
85   UIDevice *dd = [UIDevice currentDevice];
86   NSString *name = [dd name];                   // My iPhone
87   NSString *model = [dd model];                 // iPad
88   // NSString *system = [dd systemName];        // iPhone OS
89   NSString *vers = [dd systemVersion];          // 5.0
90   NSString *date =
91     [NSDateFormatter
92       localizedStringFromDate:[NSDate date]
93       dateStyle: NSDateFormatterMediumStyle
94       timeStyle: NSDateFormatterMediumStyle];
95   NSString *nl = @"\n";
96
97   NSString *result = name;
98   result = [result stringByAppendingString: nl];
99   result = [result stringByAppendingString: model];
100   // result = [result stringByAppendingString: nl];
101   // result = [result stringByAppendingString: system];
102   result = [result stringByAppendingString: @" "];
103   result = [result stringByAppendingString: vers];
104   result = [result stringByAppendingString: nl];
105   result = [result stringByAppendingString: nl];
106   result = [result stringByAppendingString: date];
107   result = [result stringByAppendingString: nl];
108   result = [result stringByAppendingString: nl];
109   return strdup ([result cStringUsingEncoding:NSISOLatin1StringEncoding]);
110 }
111
112
113 static void
114 strip_html (char *html)
115 {
116   int tag = 0;
117   int comment = 0;
118   int white = 0;
119   int nl = 0;
120   int entity = 0;
121   char *out = html;
122   for (char *in = html; *in; in++) {
123     if (comment) {
124       if (!strncmp (in, "-->", 3)) {
125         comment = 0;
126       }
127     } else if (tag) {
128       if (*in == '>') {
129         tag = 0;
130         entity = 0;
131       }
132     } else if (entity) {
133       if (*in == ';') {
134         entity = 0;
135       }
136     } else if (*in == '<') {
137       tag = 1;
138       entity = 0;
139       if (!strncmp (in, "<!--", 4)) {
140         comment = 1;
141         tag = 0;
142       } else if (!strncasecmp (in, "<BR", 3)) {
143         *out++ = '\n';
144         white = 1;
145         nl++;
146       } else if (!strncasecmp (in, "<P", 2)) {
147         if (nl < 2) { *out++ = '\n'; nl++; }
148         if (nl < 2) { *out++ = '\n'; nl++; }
149         white = 1;
150       }
151     } else if (*in == '&') {
152       char *ss = 0;
153       entity = 1;
154
155       if      (!strncmp (in, "&amp", 4))    ss = "&";
156       else if (!strncmp (in, "&lt", 3))     ss = "<";
157       else if (!strncmp (in, "&gt", 3))     ss = ">";
158       else if (!strncmp (in, "&nbsp", 5))   ss = " ";
159
160       else if (!strncmp (in, "&AElig", 6))  ss = "AE";
161       else if (!strncmp (in, "&aelig", 6))  ss = "ae";
162       else if (!strncmp (in, "&bdquo", 6))  ss = "\"";
163       else if (!strncmp (in, "&brvbar", 7)) ss = "|";
164       else if (!strncmp (in, "&bull", 5))   ss = "*";
165       else if (!strncmp (in, "&circ", 5))   ss = "^";
166       else if (!strncmp (in, "&cong", 5))   ss = "=~";
167       else if (!strncmp (in, "&copy", 5))   ss = "(c)";
168       else if (!strncmp (in, "&curren", 7)) ss = "$";
169       else if (!strncmp (in, "&deg", 4))    ss = ".";
170       else if (!strncmp (in, "&divide", 7)) ss = "/";
171       else if (!strncmp (in, "&empty", 6))  ss = "0";
172       else if (!strncmp (in, "&emsp", 5))   ss = " ";
173       else if (!strncmp (in, "&ensp", 5))   ss = " ";
174       else if (!strncmp (in, "&equiv", 6))  ss = "==";
175       else if (!strncmp (in, "&frac12", 7)) ss = "1/2";
176       else if (!strncmp (in, "&frac14", 7)) ss = "1/4";
177       else if (!strncmp (in, "&frac34", 7)) ss = "3/4";
178       else if (!strncmp (in, "&frasl", 6))  ss = "/";
179       else if (!strncmp (in, "&ge", 3))     ss = ">=";
180       else if (!strncmp (in, "&hArr", 5))   ss = "<=>";
181       else if (!strncmp (in, "&harr", 5))   ss = "<->";
182       else if (!strncmp (in, "&hellip", 7)) ss = "...";
183       else if (!strncmp (in, "&iquest", 7)) ss = "?";
184       else if (!strncmp (in, "&lArr", 5))   ss = "<=";
185       else if (!strncmp (in, "&lang", 5))   ss = "<";
186       else if (!strncmp (in, "&laquo", 6))  ss = "<<";
187       else if (!strncmp (in, "&larr", 5))   ss = "<-";
188       else if (!strncmp (in, "&ldquo", 6))  ss = "\"";
189       else if (!strncmp (in, "&le", 3))     ss = "<=";
190       else if (!strncmp (in, "&lowast", 7)) ss = "*";
191       else if (!strncmp (in, "&loz", 4))    ss = "<>";
192       else if (!strncmp (in, "&lsaquo", 7)) ss = "<";
193       else if (!strncmp (in, "&lsquo", 6))  ss = "`";
194       else if (!strncmp (in, "&macr", 5))   ss = "'";
195       else if (!strncmp (in, "&mdash", 6))  ss = "--";
196       else if (!strncmp (in, "&micro", 6))  ss = "u";
197       else if (!strncmp (in, "&middot", 7)) ss = ".";
198       else if (!strncmp (in, "&minus", 6))  ss = "-";
199       else if (!strncmp (in, "&ndash", 6))  ss = "-";
200       else if (!strncmp (in, "&ne", 3))     ss = "!=";
201       else if (!strncmp (in, "&not", 4))    ss = "!";
202       else if (!strncmp (in, "&OElig", 6))  ss = "OE";
203       else if (!strncmp (in, "&oelig", 6))  ss = "oe";
204       else if (!strncmp (in, "&ordf", 5))   ss = "_";
205       else if (!strncmp (in, "&ordm", 5))   ss = "_";
206       else if (!strncmp (in, "&para", 5))   ss = "PP";
207       else if (!strncmp (in, "&plusmn", 7)) ss = "+/-";
208       else if (!strncmp (in, "&pound", 6))  ss = "#";
209       else if (!strncmp (in, "&prime", 6))  ss = "'";
210       else if (!strncmp (in, "&quot", 5))   ss = "\"";
211       else if (!strncmp (in, "&rArr", 5))   ss = "=>";
212       else if (!strncmp (in, "&rang", 5))   ss = ">";
213       else if (!strncmp (in, "&raquo", 6))  ss = ">>";
214       else if (!strncmp (in, "&rarr", 5))   ss = "->";
215       else if (!strncmp (in, "&rdquo", 6))  ss = "\"";
216       else if (!strncmp (in, "&reg", 4))    ss = "(r)";
217       else if (!strncmp (in, "&rsaquo", 7)) ss = ">";
218       else if (!strncmp (in, "&rsquo", 6))  ss = "'";
219       else if (!strncmp (in, "&sbquo", 6))  ss = "'";
220       else if (!strncmp (in, "&sect", 5))   ss = "SS";
221       else if (!strncmp (in, "&shy", 4))    ss = "";
222       else if (!strncmp (in, "&sim", 4))    ss = "~";
223       else if (!strncmp (in, "&sup1", 5))   ss = "[1]";
224       else if (!strncmp (in, "&sup2", 5))   ss = "[2]";
225       else if (!strncmp (in, "&sup3", 5))   ss = "[3]";
226       else if (!strncmp (in, "&szlig", 6))  ss = "B";
227       else if (!strncmp (in, "&thinsp", 7)) ss = " ";
228       else if (!strncmp (in, "&thorn", 6))  ss = "|";
229       else if (!strncmp (in, "&tilde", 6))  ss = "!";
230       else if (!strncmp (in, "&times", 6))  ss = "x";
231       else if (!strncmp (in, "&trade", 6))  ss = "[tm]";
232       else if (!strncmp (in, "&uml", 4))    ss = ":";
233       else if (!strncmp (in, "&yen", 4))    ss = "Y";
234
235       if (ss) {
236         strcpy (out, ss);
237         out += strlen(ss);
238       } else if (!strncmp (in, "&#", 2)) {      // &#65;
239         int i = 0;
240         for (char *in2 = in+2; *in2 >= '0' && *in2 <= '9'; in2++)
241           i = (i * 10) + (*in2 - '0');
242         *out = (i > 255 ? '?' : i);
243       } else if (!strncmp (in, "&#x", 3)) {     // &#x41;
244         int i = 0;
245         for (char *in2 = in+3;
246              ((*in2 >= '0' && *in2 <= '9') ||
247               (*in2 >= 'A' && *in2 <= 'F') ||
248               (*in2 >= 'a' && *in2 <= 'f'));
249              in2++)
250           i = (i * 16) + (*in2 >= 'a' ? *in2 - 'a' + 16 :
251                           *in2 >= 'A' ? *in2 - 'A' + 16 :
252                           *in2 - '0');
253         *out = (i > 255 ? '?' : i);
254       } else {
255         *out++ = in[1];    // first character of entity, e.g. &eacute;.
256       }
257     } else if (*in == ' ' || *in == '\t' || *in == '\r' || *in == '\n') {
258       if (!white && out != html)
259         *out++ = ' ';
260       white = 1;
261     } else {
262       *out++ = *in;
263       white = 0;
264       nl = 0;
265     }
266   }
267   *out = 0;
268 }
269
270
271 static char *
272 copy_rss_field (const char *s)
273 {
274   if (!s) return 0;
275   while (*s && *s != '>')                       // Skip forward to >
276     s++;
277   if (! *s) return 0;
278   s++;
279
280   if (!strncmp (s, "<![CDATA[", 9)) {           // CDATA quoting
281     s += 9;
282     char *e = strstr (s, "]]");
283     if (e) *e = 0;
284     unsigned long L = strlen (s);
285     char *s2 = (char *) malloc (L+1);
286     memcpy (s2, s, L+1);
287     return s2;
288
289   } else {                                      // Entity-encoded.
290     const char *s2;
291     for (s2 = s; *s2 && *s2 != '<'; s2++)       // Terminate at <
292       ;
293     char *s3 = (char *) malloc (s2 - s + 1);
294     if (! s3) return 0;
295     memcpy (s3, s, s2-s);
296     s3[s2-s] = 0;
297     strip_html (s3);
298     return s3;
299   }
300 }
301
302
303 static char *
304 pick_rss_field (const char *a, const char *b, const char *c, const char *d)
305 {
306   // Pick the longest of the fields.
307   char *a2 = copy_rss_field (a);
308   char *b2 = copy_rss_field (b);
309   char *c2 = copy_rss_field (c);
310   char *d2 = copy_rss_field (d);
311   unsigned long al = a2 ? strlen(a2) : 0;
312   unsigned long bl = b2 ? strlen(b2) : 0;
313   unsigned long cl = c2 ? strlen(c2) : 0;
314   unsigned long dl = d2 ? strlen(d2) : 0;
315   char *ret = 0;
316
317   if      (al > bl && al > cl && al > dl) ret = a2;
318   else if (bl > al && bl > cl && bl > dl) ret = b2;
319   else if (cl > al && cl > bl && cl > dl) ret = c2;
320   else ret = d2;
321   if (a2 && a2 != ret) free (a2);
322   if (b2 && b2 != ret) free (b2);
323   if (c2 && c2 != ret) free (c2);
324   if (d2 && d2 != ret) free (d2);
325   return ret;
326 }
327
328
329 static void
330 strip_rss (char *rss)
331 {
332   char *out = rss;
333   const char *a = 0, *b = 0, *c = 0, *d = 0, *t = 0;
334   int head = 1;
335   int done = 0;
336
337   for (char *in = rss; *in; in++) {
338     if (*in == '<') {
339       if (!strncasecmp (in, "<item", 5) ||      // New item, dump.
340           !strncasecmp (in, "<entry", 6)) {
341       DONE:
342         head = 0;
343         char *title = copy_rss_field (t);
344         char *body  = pick_rss_field (a, b, c, d);
345
346         a = b = c = d = t = 0;
347
348         if (title && body && !strcmp (title, body)) {
349           free (title);
350           title = 0;
351         }
352
353         if (title) {
354           strcpy (out, title);
355           free (title);
356           out += strlen (out);
357           strcpy (out, "\n\n");
358           out += strlen (out);
359         }
360
361         if (body) {
362           strcpy (out, body);
363           free (body);
364           out += strlen (out);
365           strcpy (out, "<P>");
366           out += strlen (out);
367         }
368
369       } else if (head) {   // still before first <item>
370         ;
371       } else if (!strncasecmp (in, "<title", 6)) {
372         t = in+6;
373       } else if (!strncasecmp (in, "<summary", 8)) {
374         d = in+8;
375       } else if (!strncasecmp (in, "<description", 12)) {
376         a = in+12;
377       } else if (!strncasecmp (in, "<content:encoded", 16)) {
378         c = in+16;
379       } else if (!strncasecmp (in, "<content", 8)) {
380         b = in+8;
381       }
382     }
383   }
384
385   if (! done) {         // Finish off the final item.
386     done = 1;
387     goto DONE;
388   }
389
390   // Now decode it a second time.
391   strip_html (rss);
392 }
393
394
395 static void
396 wrap_text (char *body, int columns, int max_lines)
397 {
398   int col = 0, last_col = 0;
399   char *last_space = 0;
400   int lines = 0;
401   if (! body) return;
402   for (char *p = body; *p; p++) {
403     if (*p == '\r' || *p == '\n' || *p == ' ' || *p == '\t') {
404       if (col > columns && last_space) {
405         *last_space = '\n';
406         col = col - last_col;
407       }
408       last_space = p;
409       last_col = col;
410     }
411     if (*p == '\r' || *p == '\n') {
412       col = 0;
413       last_col = 0;
414       last_space = 0;
415       lines++;
416       if (max_lines && lines >= max_lines)
417         {
418           *p = 0;
419           break;
420         }
421     } else {
422       col++;
423     }
424   }
425 }
426
427
428 static void
429 rewrap_text (char *body, int columns)
430 {
431   if (! body) return;
432   for (char *p = body; *p; p++) {
433     if (*p == '\n') {
434       if (p[1] == '\n')
435         p++;
436       else
437         *p = ' ';
438     }
439   }
440   wrap_text (body, columns, 0);
441 }
442
443
444
445 static void
446 strip_backslashes (char *s)
447 {
448   char *out = s;
449   for (char *in = s; *in; in++) {
450     if (*in == '\\') {
451       in++;
452       if      (*in == 'n') *out++ = '\n';
453       else if (*in == 'r') *out++ = '\r';
454       else if (*in == 't') *out++ = '\t';
455       else *out++ = *in;
456     } else {
457       *out++ = *in;
458     }
459   }
460   *out = 0;
461 }
462
463
464 static char *
465 url_string (const char *url)
466 {
467   NSURL *nsurl =
468     [NSURL URLWithString:
469              [NSString stringWithCString: url
470                        encoding:NSISOLatin1StringEncoding]];
471   NSString *body =
472     [NSString stringWithContentsOfURL: nsurl
473               encoding: NSISOLatin1StringEncoding
474               error: nil];
475   if (! body)
476     return 0;
477
478   enum { RSS, HTML, TEXT } type;
479
480   // Only search the first 1/2 K of the document while determining type.
481
482   unsigned long L = [body length];
483   if (L > 512) L = 512;
484   NSString *head = [[[body substringToIndex: L]
485                       stringByTrimmingCharactersInSet:
486                         [NSCharacterSet whitespaceAndNewlineCharacterSet]]
487                      lowercaseString];
488   if ([head hasPrefix:@"<?xml"] ||
489       [head hasPrefix:@"<!doctype rss"])
490     type = RSS;
491   else if ([head hasPrefix:@"<!doctype html"] ||
492            [head hasPrefix:@"<html"] ||
493            [head hasPrefix:@"<head"])
494     type = HTML;
495   else if ([head rangeOfString:@"<base"].length ||
496            [head rangeOfString:@"<body"].length ||
497            [head rangeOfString:@"<script"].length ||
498            [head rangeOfString:@"<style"].length ||
499            [head rangeOfString:@"<a href"].length)
500     type = HTML;
501   else if ([head rangeOfString:@"<channel"].length ||
502            [head rangeOfString:@"<generator"].length ||
503            [head rangeOfString:@"<description"].length ||
504            [head rangeOfString:@"<content"].length ||
505            [head rangeOfString:@"<feed"].length ||
506            [head rangeOfString:@"<entry"].length)
507     type = RSS;
508   else
509     type = TEXT;
510
511   char *body2 = strdup ([body cStringUsingEncoding:NSISOLatin1StringEncoding]);
512
513   switch (type) {
514   case HTML: strip_html (body2); break;
515   case RSS:  strip_rss (body2);  break;
516   case TEXT: break;
517   default: abort(); break;
518   }
519
520   return body2;
521 }
522
523
524 int
525 textclient_getc (text_data *d)
526 {
527   if (!d->fp || !*d->fp) {
528     if (d->buf) {
529       free (d->buf);
530       d->buf = 0;
531       d->fp = 0;
532     }
533     switch (d->mode) {
534     case DATE: DATE:
535       d->buf = date_string();
536       break;
537     case LITERAL:
538       if (!d->literal || !*d->literal)
539         goto DATE;
540       d->buf = (char *) malloc (strlen (d->literal) + 3);
541       strcpy (d->buf, d->literal);
542       strcat (d->buf, "\n");
543       strip_backslashes (d->buf);
544       d->fp = d->buf;
545       break;
546     case URL:
547       if (!d->url || !*d->url)
548         goto DATE;
549       d->buf = url_string (d->url);
550       break;
551     default:
552       abort();
553     }
554     if (d->columns > 10)
555       wrap_text (d->buf, d->columns, d->max_lines);
556     d->fp = d->buf;
557   }
558
559   if (!d->fp || !*d->fp)
560     return -1;
561
562   unsigned char c = (unsigned char) *d->fp++;
563   return (int) c;
564 }
565
566
567 Bool
568 textclient_putc (text_data *d, XKeyEvent *k)
569 {
570   return False;
571 }
572
573
574 void
575 textclient_reshape (text_data *d,
576                     int pix_w, int pix_h,
577                     int char_w, int char_h,
578                     int max_lines)
579 {
580   d->columns = char_w;
581   d->max_lines = max_lines;
582   rewrap_text (d->buf, d->columns);
583 }
584
585 #endif /* USE_IPHONE -- whole file */