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