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