OSDN Git Service

* Added chxj_css_parse_from_uri.
[modchxj/mod_chxj.git] / src / chxj_css.c
1 /*
2  * Copyright (C) 2005-2008 Atsushi Konno All rights reserved.
3  * Copyright (C) 2005 QSDN,Inc. All rights reserved.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 #include "chxj_css.h"
18 #include "chxj_serf.h"
19 #include "chxj_encoding.h"
20 #include "qs_parse_string.h"
21 #include "apr_pools.h"
22
23 #include <libgen.h>
24
25 #undef list_insert
26 #undef list_remove
27 #define list_insert(node, point) do {           \
28     node->ref  = point->ref;                    \
29     *node->ref = node;                          \
30     node->next = point;                         \
31     point->ref = &node->next;                   \
32 } while (0)
33
34 #define list_remove(node) do {                  \
35     *node->ref      = node->next;               \
36     node->next->ref = node->ref;                \
37 } while (0)
38
39 /*===========================================================================*/
40 /* PARSER                                                                    */
41 /*===========================================================================*/
42 struct css_already_import_stack {
43   char *full_url;
44   struct css_already_import_stack *next;
45   struct css_already_import_stack **ref;
46 };
47
48 struct css_app_data {
49   css_stylesheet_t *stylesheet;
50   char **selector_list;
51   int selector_count;
52   apr_pool_t *pool;
53   request_rec *r;
54   int error_occured;
55   css_property_t property_head;
56   struct css_already_import_stack imported_stack_head;
57 };
58 static char *s_css_parser_get_charset(apr_pool_t *pool, const char *src, apr_size_t *next_pos);
59 static void s_css_parser_from_uri_start_selector(CRDocHandler * a_this, CRSelector *a_selector_list);
60 static void s_css_parser_from_uri_end_selector(CRDocHandler * a_this, CRSelector *a_selector_list);
61 static void s_css_parser_from_uri_property(CRDocHandler *a_this, CRString *a_name, CRTerm *a_expression, gboolean a_is_important);
62 static css_property_t *s_css_parser_copy_property(apr_pool_t *pool, css_property_t *from);
63 static css_selector_t *s_new_selector(apr_pool_t *pool, css_stylesheet_t *stylesheet, char *name);
64 static css_selector_t *s_search_selector(css_stylesheet_t *stylesheet, const char *name);
65 static void s_merge_property(css_selector_t *sel, css_property_t *tgt);
66 static void s_css_parser_from_uri_import_style(CRDocHandler *a_this, GList *a_media_list, CRString *a_uri, CRString *a_uri_default_ns, CRParsingLocation *a_location);
67 static char *s_path_to_fullurl(apr_pool_t *pool, const char *base_url, const char *base_path, const char *uri);
68 static char *s_uri_to_base_url(apr_uri_t *uri, apr_pool_t *pool);
69 static css_stylesheet_t *s_chxj_css_parse_from_uri(request_rec *r, apr_pool_t *pool, struct css_already_import_stack *imported_stack, css_stylesheet_t *old_stylesheet, const char *uri);
70 static int s_is_already_imported(struct css_already_import_stack *imported_stack_head, const char *url);
71 static css_stylesheet_t *s_merge_stylesheet(apr_pool_t *pool, css_stylesheet_t *old_stylesheet, css_stylesheet_t *new_stylesheet);
72 static void s_copy_already_import_stack(apr_pool_t *pool, struct css_already_import_stack *base, struct css_already_import_stack *imported_stack);
73
74 css_stylesheet_t *
75 chxj_css_parse_from_uri(request_rec *r, apr_pool_t *pool, css_stylesheet_t *old_stylesheet, const char *uri)
76 {
77   css_stylesheet_t *new_stylesheet = s_chxj_css_parse_from_uri(r, pool, NULL, old_stylesheet, uri);
78   chxj_css_stylesheet_dump(new_stylesheet);
79   return new_stylesheet;
80 }
81
82 static css_stylesheet_t *
83 s_chxj_css_parse_from_uri(request_rec *r, apr_pool_t *pool, struct css_already_import_stack *imported_stack, css_stylesheet_t *old_stylesheet, const char *uri)
84 {
85   CRParser     *parser      = NULL;
86   CRDocHandler *sac_handler = NULL;
87   enum CRStatus ret;
88   char         *css         = NULL;
89   char         *charset     = NULL;
90   char         *full_url    = NULL;
91   apr_size_t  srclen;
92   apr_size_t  next_pos;
93   css_stylesheet_t *stylesheet = NULL;
94   struct css_already_import_stack *new_stack;
95   struct css_app_data app_data;
96   char         *base_url;
97   
98
99   DBG(r, "start chxj_css_parse_from_uri() uri:[%s]", uri);
100
101   base_url = s_uri_to_base_url(&r->parsed_uri, pool);
102   full_url = s_path_to_fullurl(pool, base_url, r->parsed_uri.path, uri);
103
104   /* check already import */
105   if (imported_stack && s_is_already_imported(imported_stack, full_url)) {
106     DBG(r, "end chxj_css_parse_from_uri(): already imported:[%s]", full_url); 
107     return NULL;
108   }
109
110   /* GET request */
111   css = chxj_serf_get(r, pool, full_url);
112   if (css == NULL) {
113     ERR(r, "%s:%d end chxj_css_parse_from_uri(): serf_get failed: url:[%s]", APLOG_MARK, uri);
114     return NULL;
115   }
116   srclen = strlen(css);
117   
118   /* convert encoding */
119   charset = s_css_parser_get_charset(pool, css, &next_pos);
120   if (charset) {
121     DBG(r, "charset:[%s]\n", charset);
122     css += next_pos;
123     srclen = strlen(css);
124     css = chxj_iconv(r, pool, css, &srclen, charset, "UTF-8");
125   }
126
127   /* create parser */
128   parser = cr_parser_new_from_buf((guchar *)css, srclen, CR_UTF_8, FALSE);
129   if (!parser) {
130     ERR(r, "%s:%d end chxj_css_parse_from_uri(): cr_parser_new_from_buf() failed", APLOG_MARK);
131     return NULL;
132   }
133   sac_handler = cr_doc_handler_new();
134   if (!sac_handler) {
135     ERR(r, "%s:%d end chxj_css_parse_from_uri(): cr_doc_handler_new() failed", APLOG_MARK);
136     cr_parser_destroy(parser);
137     return NULL;
138   }
139
140   stylesheet = apr_palloc(pool, sizeof(*stylesheet));
141   memset(stylesheet, 0, sizeof(*stylesheet));
142   stylesheet->selector_head.next = &stylesheet->selector_head;
143   stylesheet->selector_head.ref  = &stylesheet->selector_head.next;
144
145   memset(&app_data, 0, sizeof(struct css_app_data));
146   app_data.stylesheet     = stylesheet;
147   app_data.selector_list  = NULL;
148   app_data.selector_count = 0;
149   app_data.pool           = pool;
150   app_data.error_occured  = 0;
151   app_data.r              = r;
152   if (imported_stack) {
153     s_copy_already_import_stack(pool, &app_data.imported_stack_head, imported_stack);
154   }
155   else {
156     app_data.imported_stack_head.next = &app_data.imported_stack_head;
157     app_data.imported_stack_head.ref  = &app_data.imported_stack_head.next;
158   }
159   new_stack = apr_palloc(pool, sizeof(*new_stack));
160   memset(new_stack, 0, sizeof(*new_stack));
161   new_stack->next = new_stack;
162   new_stack->ref  = &new_stack->next;
163   new_stack->full_url = full_url;
164   list_insert(new_stack, (&app_data.imported_stack_head));
165
166   sac_handler->app_data = &app_data;
167
168   sac_handler->start_selector = s_css_parser_from_uri_start_selector;
169   sac_handler->end_selector   = s_css_parser_from_uri_end_selector;
170   sac_handler->property       = s_css_parser_from_uri_property;
171   sac_handler->import_style   = s_css_parser_from_uri_import_style;
172
173   ret = cr_parser_set_sac_handler(parser, sac_handler);
174   if (ret != CR_OK) {
175     ERR(r, "%s:%d end chxj_css_parse_from_uri(): cr_parser_set_sac_handler() failed: ret:[%d]", APLOG_MARK, ret);
176     cr_parser_destroy(parser);
177     return NULL;
178   }
179
180   ret = cr_parser_parse(parser);
181   cr_parser_destroy(parser);
182   DBG(r, "end chxj_css_parse_from_uri() url:[%s]", uri);
183   return s_merge_stylesheet(pool, old_stylesheet, app_data.stylesheet);
184 }
185
186
187 #define ERROR_OCCORED do {          \
188     if (app_data->error_occured) {  \
189       return;                       \
190     }                               \
191   }                                 \
192   while (0)
193
194
195 #define CB_INIT \
196   struct css_app_data *app_data = (struct css_app_data *)a_this->app_data
197
198
199
200 static void 
201 s_css_parser_from_uri_start_selector(CRDocHandler * a_this, CRSelector *a_selector_list)
202 {
203   int ii;
204   CRSelector *cur = NULL;
205   CB_INIT;
206   ERROR_OCCORED;
207
208   app_data->selector_count = 0;
209   for (cur = a_selector_list; cur; cur = cur->next)
210     app_data->selector_count++;
211
212   app_data->selector_list = apr_palloc(app_data->pool, sizeof(char *) * app_data->selector_count);
213   if (! app_data->selector_list) {
214     ERR(app_data->r, "%s:%d Out of memory", APLOG_MARK);
215     app_data->error_occured = 1;
216     return;
217   }
218
219   ii = 0;
220   for (cur = a_selector_list; cur; cur = cur->next) {
221     if (cur->simple_sel) {
222       guchar *tmp_str = cr_simple_sel_to_string(cur->simple_sel);
223       if (tmp_str) {
224         app_data->selector_list[ii++] = apr_pstrdup(app_data->pool, (char *)tmp_str);
225         g_free (tmp_str);
226         tmp_str = NULL;
227       }
228     }
229   }
230   app_data->property_head.next = &app_data->property_head;
231   app_data->property_head.ref  = &app_data->property_head.next;
232 }
233
234
235 static void 
236 s_css_parser_from_uri_end_selector(CRDocHandler * a_this, CRSelector *a_selector_list)
237 {
238   int ii;
239   css_property_t *cur = NULL;
240   CB_INIT;
241   ERROR_OCCORED;
242
243   if (app_data->property_head.next)  {
244     for (ii=0; ii<app_data->selector_count; ii++) {
245       css_selector_t *sel = s_new_selector(app_data->pool, app_data->stylesheet, app_data->selector_list[ii]); 
246
247       for (cur = app_data->property_head.next; cur && cur != &app_data->property_head; cur = cur->next) {
248         css_property_t *tgt = s_css_parser_copy_property(app_data->pool, cur);
249         css_property_t *pnt = &sel->property_head;
250         s_merge_property(sel, tgt);
251       }
252       css_selector_t *point_selector = &app_data->stylesheet->selector_head;
253       list_insert(sel, point_selector);
254     }
255   }
256   app_data->property_head.next = &app_data->property_head;
257   app_data->property_head.ref  = &app_data->property_head.next;
258 }
259
260 static void
261 s_merge_property(css_selector_t *sel, css_property_t *tgt)
262 {
263   css_property_t *cur;
264   css_property_t *pnt = &sel->property_head;
265   char l = tolower(*tgt->name);
266   char u = toupper(*tgt->name);
267   for (cur = pnt->next; cur != pnt;cur = cur->next) {
268     if ((l == *cur->name || u == *cur->name) && strcasecmp(cur->name, tgt->name) == 0) {
269       cur->value = tgt->value;
270       return;
271     }
272   }
273   list_insert(tgt,  pnt);
274 }
275
276 static css_selector_t *
277 s_new_selector(apr_pool_t *pool, css_stylesheet_t *stylesheet, char *name)
278 {
279   css_selector_t *sel = NULL;
280   sel = s_search_selector(stylesheet, name);
281   if (sel) {
282     list_remove(sel);
283     sel->next = sel;
284     sel->ref = &sel->next;
285   }
286   else {
287     sel = apr_palloc(pool, sizeof(css_selector_t));
288     memset(sel, 0, sizeof(css_selector_t));
289     sel->name = name;
290     sel->next = sel;
291     sel->ref = &sel->next;
292     sel->property_head.next = &sel->property_head;
293     sel->property_head.ref = &sel->property_head.next;
294   }
295   return sel;
296 }
297
298 static css_selector_t *
299 s_search_selector(css_stylesheet_t *stylesheet, const char *name)
300 {
301   css_selector_t *cur;
302   char l = tolower(*name);
303   char u = toupper(*name);
304   if (! stylesheet) return NULL;
305   for (cur = stylesheet->selector_head.next; cur != &stylesheet->selector_head; cur = cur->next) {
306     if ((l == *cur->name || u == *cur->name) && strcasecmp(cur->name, name) == 0) {
307       return cur;
308     }
309   }
310   return NULL;
311 }
312
313 static css_property_t *
314 s_css_parser_copy_property(apr_pool_t *pool, css_property_t *from)
315 {
316   css_property_t *prop = apr_palloc(pool, sizeof(css_property_t));
317   prop->name  = apr_pstrdup(pool, from->name);
318   prop->value = apr_pstrdup(pool, from->value);
319   prop->next  = prop;
320   prop->ref   = &prop->next;
321   return prop;
322 }
323
324
325
326
327 static void
328 s_css_parser_from_uri_property(CRDocHandler *a_this, CRString *a_name, CRTerm *a_expression, gboolean a_is_important)
329 {
330   CB_INIT;
331   ERROR_OCCORED;
332   css_property_t *property;
333
334   if (a_name && a_expression) {
335     guchar *tmp_str;
336     property = apr_palloc(app_data->pool, sizeof(*property));
337     memset(property, 0, sizeof(*property));
338     property->name = apr_pstrdup(app_data->pool, cr_string_peek_raw_str(a_name));
339     tmp_str = cr_term_one_to_string(a_expression);
340     property->value = apr_pstrdup(app_data->pool, (char *)tmp_str);
341     g_free(tmp_str);
342     tmp_str = NULL;
343
344     css_property_t *point_property = &app_data->property_head;
345     list_insert(property, point_property);
346   }
347 }
348
349
350 static char *
351 s_css_parser_get_charset(apr_pool_t *pool, const char *src, apr_size_t *next_pos)
352 {
353   register char *p = (char *)src;
354   char *sv;
355   char *ret = NULL;
356    
357   if (! p) {
358     return NULL;
359   }
360
361   for (; *p && is_white_space(*p); p++)
362     ;
363
364 #define CUT_TOKEN(X) \
365         do { \
366           sv = ++p; \
367           for (;*p && (X); p++) \
368             ; \
369           ret = apr_palloc(pool, p - sv + 1); \
370           memset(ret, 0, p - sv + 1); \
371           memcpy(ret, sv, p - sv);  \
372         } \
373         while (0)
374
375   if (*p == '@') {
376     if (strncasecmp(p, "@charset", sizeof("@charset")-1) == 0) {
377       p += sizeof("@charset");
378       for (; *p && is_white_space(*p); p++)
379         ;
380       if (*p == '"') {
381         CUT_TOKEN(*p != '"');
382         if (! *p) return NULL;
383       }
384       else if (*p == '\'') {
385         CUT_TOKEN(*p != '\'');
386         if (! *p) return NULL;
387       }
388       else {
389         CUT_TOKEN(! is_white_space(*p));
390         if (! *p) return NULL;
391       }
392     }
393   }
394   if (ret) {
395     *next_pos = p - src + 1;
396   }
397   else {
398     *next_pos = 0;
399   }
400 #undef CUT_TOKEN
401   return ret;
402 }
403
404
405 static void
406 s_css_parser_from_uri_import_style(CRDocHandler *a_this, GList *a_media_list, CRString *a_uri, CRString *a_uri_default_ns, CRParsingLocation *a_location) 
407 {
408   CB_INIT;
409   ERROR_OCCORED;
410   guint ii = 0;
411   guint len = g_list_length(a_media_list);
412   int flag = 0;
413   css_stylesheet_t *new_stylesheet = NULL;
414
415   for (ii=0; ii<len; ii++) {
416     char *str = cr_string_peek_raw_str(g_list_nth_data(a_media_list, ii));
417     if (('h' == *str || 'H' == *str) && strcasecmp(str, "handheld") == 0) {
418       flag = 1;
419       break;
420     }
421     if (('a' == *str || 'A' == *str) && strcasecmp(str, "all") == 0) {
422       flag = 1;
423       break;
424     }
425   }
426   if (flag || len == 0) {
427     if (a_uri) {
428       apr_uri_t uri;
429       char      *new_url = NULL;
430       char      *import_url = cr_string_peek_raw_str(a_uri);
431       char      *base_url = NULL;
432
433       base_url = s_uri_to_base_url(&app_data->r->parsed_uri, app_data->pool);
434       new_url = s_path_to_fullurl(app_data->pool, base_url, app_data->r->parsed_uri.path, import_url);
435       
436       new_stylesheet = s_chxj_css_parse_from_uri(app_data->r, app_data->pool, &app_data->imported_stack_head, app_data->stylesheet, new_url);
437       if (new_stylesheet) {
438         app_data->stylesheet = new_stylesheet;
439       }
440     }
441   }
442 }
443
444
445 static char *
446 s_path_to_fullurl(apr_pool_t *pool, const char *base_url, const char *base_path, const char *uri)
447 {
448   char *new_url = NULL;
449   if (chxj_starts_with(uri, "http")) {
450     return uri;
451   }
452
453   if (*uri == '/') {
454     return apr_pstrcat(pool, base_url, uri, NULL);
455   }
456
457   new_url = apr_pstrcat(pool, base_url, base_path, NULL);
458   if (new_url[strlen(new_url)-1] == '/') {
459     new_url = apr_pstrcat(pool, new_url, uri, NULL);
460   }
461   else {
462     new_url = apr_pstrcat(pool, new_url, "/", uri, NULL);
463   }
464   return new_url;
465 }
466
467
468 static char *
469 s_uri_to_base_url(apr_uri_t *uri, apr_pool_t *pool)
470 {
471   char *new_url = apr_psprintf(pool, "%s://%s", uri->scheme, uri->hostname);
472   if (strcmp(uri->scheme, "http") == 0) {
473     if (uri->port != 80 && uri->port != 0) {
474       new_url = apr_pstrcat(pool, new_url, apr_psprintf(pool, ":%d", uri->port), NULL);
475     }
476   }
477   else if (strcmp(uri->scheme, "https") == 0) {
478     if (uri->port != 443 && uri->port != 0) {
479       new_url = apr_pstrcat(pool, new_url, apr_psprintf(pool, ":%d", uri->port), NULL);
480     }
481   }
482   return new_url;
483 }
484
485 static int
486 s_is_already_imported(struct css_already_import_stack *imported_stack_head, const char *url)
487 {
488   struct css_already_import_stack *cur;
489   char l = tolower(*url);
490   char u = toupper(*url);
491   for (cur = imported_stack_head->next; cur != imported_stack_head; cur = cur->next) {
492     if ((l == *cur->full_url || u == *cur->full_url) && strcasecmp(url, cur->full_url) == 0) {
493       return 1;
494     }
495   }
496   return 0;
497 }
498
499 static css_stylesheet_t *
500 s_merge_stylesheet(apr_pool_t *pool, css_stylesheet_t *old_stylesheet, css_stylesheet_t *new_stylesheet)
501 {
502   css_selector_t *cur;
503   if (! old_stylesheet) {
504     return new_stylesheet;
505   }
506
507   for (cur = new_stylesheet->selector_head.next; cur != &new_stylesheet->selector_head; cur = cur->next) {
508     char *name = cur->name;
509     char l = tolower(*name);
510     char u = toupper(*name);
511     css_selector_t *cur_old;
512     int found;
513
514     found = 0;
515     for (cur_old = old_stylesheet->selector_head.next; cur_old != &old_stylesheet->selector_head; cur_old = cur_old->next) {
516       char *oldname = cur_old->name;
517       if ((l == *oldname || u == *oldname) && strcasecmp(name, oldname) == 0) {
518         css_property_t *cur_prop;
519         for (cur_prop = cur->property_head.next; cur_prop != &cur->property_head; cur_prop = cur_prop->next) {
520           css_property_t *target = s_css_parser_copy_property(pool, cur_prop);
521           s_merge_property(cur_old, target);
522         }
523         found = 1;
524         break;
525       }
526     }
527     if (! found) {
528       /* add new selector */
529       css_property_t *cur_prop;
530       css_selector_t *new_selector =  apr_palloc(pool, sizeof(*new_selector));
531       memset(new_selector, 0, sizeof(*new_selector));
532       new_selector->next = new_selector;
533       new_selector->ref  = &new_selector->next;
534       new_selector->property_head.next = &new_selector->property_head;
535       new_selector->property_head.ref  = &new_selector->property_head.next;
536       new_selector->name = apr_pstrdup(pool, name);
537       for (cur_prop = cur->property_head.next; cur_prop != &cur->property_head; cur_prop = cur_prop->next) {
538         css_property_t *target = s_css_parser_copy_property(pool, cur_prop);
539         list_insert(target, (&new_selector->property_head));
540       }
541       list_insert(new_selector, (&old_stylesheet->selector_head));
542     }
543   }
544
545   return old_stylesheet;
546 }
547
548
549 static void
550 s_copy_already_import_stack(apr_pool_t *pool, struct css_already_import_stack *base, struct css_already_import_stack *imported_stack)
551 {
552   struct css_already_import_stack *cur;
553
554   base->next = base;
555   base->ref  = &base->next;
556   
557   for (cur = imported_stack->next; cur != imported_stack; cur = cur->next) {
558     struct css_already_import_stack *new_stack;
559     new_stack = apr_palloc(pool, sizeof(*new_stack));
560     memset(new_stack, 0, sizeof(*new_stack));
561     new_stack->full_url = apr_pstrdup(pool, cur->full_url);
562     list_insert(new_stack, base);
563   }
564 }
565
566 /* For DEBUG */
567 void
568 chxj_css_stylesheet_dump(css_stylesheet_t *stylesheet)
569 {
570   css_selector_t *cur_sel; 
571   css_property_t *cur_prop;
572
573   for (cur_sel = stylesheet->selector_head.next; cur_sel != &stylesheet->selector_head; cur_sel = cur_sel->next) {
574     fprintf(stderr, "selector:[%s]\n", cur_sel->name);
575     for (cur_prop = cur_sel->property_head.next; cur_prop != &cur_sel->property_head; cur_prop = cur_prop->next) {
576       fprintf(stderr, "\tproperty:\n");
577       fprintf(stderr, "\t\t- name:%s\n", cur_prop->name);
578       fprintf(stderr, "\t\t- value:%s\n", cur_prop->value);
579     }
580   }
581 }
582
583 #if 0
584 css_stylesheet_t *
585 chxj_css_parse_from_style_tag(apr_pool_t *pool, css_stylesheet_t *old_stylesheet, const char *style_value)
586 {
587 }
588
589
590 css_stylesheet_t *
591 chxj_css_parse_from_style_attribute(apr_pool_t *pool, css_stylesheet_t *old_stylesheet, const char *style_attribute_value)
592 {
593
594 /*===========================================================================*/
595 /* find selector                                                             */
596 /*===========================================================================*/
597 css_selector_t *
598 chxj_css_find_selector(apr_pool_t *pool, css_stylesheet_t *stylesheet, const char *tag_name, const char *class_name, const char *id)
599 {
600 }
601
602 /*===========================================================================*/
603 /* push/pop current_stylesheet_stack                                         */
604 /*===========================================================================*/
605 #endif
606
607 /*
608  * vim:ts=2 et
609  */