Code

Cleaned up more of the gboolean to bool janitorial task...great!
[inkscape.git] / src / sp-style-elem.cpp
1 #include <libcroco/cr-parser.h>
2 #include "xml/node-event-vector.h"
3 #include "xml/repr.h"
4 #include "document.h"
5 #include "sp-style-elem.h"
6 #include "attributes.h"
7 using Inkscape::XML::TEXT_NODE;
9 static void sp_style_elem_init(SPStyleElem *style_elem);
10 static void sp_style_elem_class_init(SPStyleElemClass *klass);
11 static void sp_style_elem_build(SPObject *object, SPDocument *doc, Inkscape::XML::Node *repr);
12 static void sp_style_elem_set(SPObject *object, unsigned const key, gchar const *const value);
13 static void sp_style_elem_read_content(SPObject *);
14 static Inkscape::XML::Node *sp_style_elem_write(SPObject *, Inkscape::XML::Node *, guint flags);
16 static SPObjectClass *parent_class;
18 GType
19 sp_style_elem_get_type()
20 {
21     static GType type = 0;
22     if (!type) {
23         GTypeInfo info = {
24             sizeof(SPStyleElemClass),
25             NULL,   /* base_init */
26             NULL,   /* base_finalize */
27             (GClassInitFunc) sp_style_elem_class_init,
28             NULL,   /* class_finalize */
29             NULL,   /* class_data */
30             sizeof(SPStyleElem),
31             16,     /* n_preallocs */
32             (GInstanceInitFunc) sp_style_elem_init,
33             NULL,   /* value_table */
34         };
35         type = g_type_register_static(SP_TYPE_OBJECT, "SPStyleElem", &info, (GTypeFlags) 0);
36     }
38     return type;
39 }
41 static void
42 sp_style_elem_class_init(SPStyleElemClass *klass)
43 {
44     parent_class = (SPObjectClass *)g_type_class_ref(SP_TYPE_OBJECT);
45     /* FIXME */
47     klass->build = sp_style_elem_build;
48     klass->set = sp_style_elem_set;
49     klass->read_content = sp_style_elem_read_content;
50     klass->write = sp_style_elem_write;
51 }
53 static void
54 sp_style_elem_init(SPStyleElem *style_elem)
55 {
56     media_set_all(style_elem->media);
57     style_elem->is_css = false;
58 }
60 static void
61 sp_style_elem_set(SPObject *object, unsigned const key, gchar const *const value)
62 {
63     g_return_if_fail(object);
64     SPStyleElem &style_elem = *SP_STYLE_ELEM(object);
66     switch (key) {
67         case SP_ATTR_TYPE: {
68             if (!value) {
69                 /* TODO: `type' attribute is required.  Give error message as per
70                    http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing. */
71                 style_elem.is_css = false;
72             } else {
73                 /* fixme: determine what whitespace is allowed.  Will probably need to ask on SVG
74                  * list; though the relevant RFC may give info on its lexer. */
75                 style_elem.is_css = ( g_ascii_strncasecmp(value, "text/css", 8) == 0
76                                       && ( value[8] == '\0' ||
77                                            value[8] == ';'    ) );
78             }
79             break;
80         }
82 #if 0 /* unfinished */
83         case SP_ATTR_MEDIA: {
84             parse_media(style_elem, value);
85             break;
86         }
87 #endif
89         /* title is ignored. */
90         default: {
91             if (parent_class->set) {
92                 parent_class->set(object, key, value);
93             }
94             break;
95         }
96     }
97 }
99 static void
100 child_add_rm_cb(Inkscape::XML::Node *, Inkscape::XML::Node *, Inkscape::XML::Node *,
101                 void *const data)
103     sp_style_elem_read_content(static_cast<SPObject *>(data));
106 static void
107 content_changed_cb(Inkscape::XML::Node *, gchar const *, gchar const *,
108                    void *const data)
110     sp_style_elem_read_content(static_cast<SPObject *>(data));
113 static void
114 child_order_changed_cb(Inkscape::XML::Node *, Inkscape::XML::Node *,
115                        Inkscape::XML::Node *, Inkscape::XML::Node *,
116                        void *const data)
118     sp_style_elem_read_content(static_cast<SPObject *>(data));
121 static Inkscape::XML::Node *
122 sp_style_elem_write(SPObject *const object, Inkscape::XML::Node *repr, guint const flags)
124     if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
125         repr = sp_repr_new("svg:style");
126     }
128     g_return_val_if_fail(object, repr);
129     SPStyleElem &style_elem = *SP_STYLE_ELEM(object);
130     if (flags & SP_OBJECT_WRITE_BUILD) {
131         g_warning("nyi: Forming <style> content for SP_OBJECT_WRITE_BUILD.");
132         /* fixme: Consider having the CRStyleSheet be a member of SPStyleElem, and then
133            pretty-print to a string s, then repr->addChild(sp_repr_new_text(s), NULL). */
134     }
135     if (style_elem.is_css) {
136         repr->setAttribute("type", "text/css");
137     }
138     /* todo: media */
140     if (((SPObjectClass *) parent_class)->write)
141         ((SPObjectClass *) parent_class)->write(object, repr, flags);
143     return repr;
147 /** Returns the concatenation of the content of the text children of the specified object. */
148 static GString *
149 concat_children(Inkscape::XML::Node const &repr)
151     GString *ret = g_string_sized_new(0);
152     // effic: 0 is just to catch bugs.  Increase to something reasonable.
153     for (Inkscape::XML::Node const *rch = repr.firstChild(); rch != NULL; rch = rch->next()) {
154         if ( rch->type() == TEXT_NODE ) {
155             ret = g_string_append(ret, rch->content());
156         }
157     }
158     return ret;
163 /* Callbacks for SAC-style libcroco parser. */
165 enum StmtType { NO_STMT, FONT_FACE_STMT, NORMAL_RULESET_STMT };
167 struct ParseTmp
169     CRStyleSheet *const stylesheet;
170     StmtType stmtType;
171     CRStatement *currStmt;
172     unsigned magic;
173     static unsigned const ParseTmp_magic = 0x23474397;  // from /dev/urandom
175     ParseTmp(CRStyleSheet *const stylesheet) :
176         stylesheet(stylesheet),
177         stmtType(NO_STMT),
178         currStmt(NULL),
179         magic(ParseTmp_magic)
180     { }
182     bool hasMagic() const {
183         return magic == ParseTmp_magic;
184     }
186     ~ParseTmp()
187     {
188         g_return_if_fail(hasMagic());
189         magic = 0;
190     }
191 };
193 static void
194 start_selector_cb(CRDocHandler *a_handler,
195                   CRSelector *a_sel_list)
197     g_return_if_fail(a_handler && a_sel_list);
198     g_return_if_fail(a_handler->app_data != NULL);
199     ParseTmp &parse_tmp = *static_cast<ParseTmp *>(a_handler->app_data);
200     g_return_if_fail(parse_tmp.hasMagic());
201     if ( (parse_tmp.currStmt != NULL)
202          || (parse_tmp.stmtType != NO_STMT) ) {
203         g_warning("Expecting currStmt==NULL and stmtType==0 (NO_STMT) at start of ruleset, but found currStmt=%p, stmtType=%u",
204                   static_cast<void *>(parse_tmp.currStmt), unsigned(parse_tmp.stmtType));
205         // fixme: Check whether we need to unref currStmt if non-NULL.
206     }
207     CRStatement *ruleset = cr_statement_new_ruleset(parse_tmp.stylesheet, a_sel_list, NULL, NULL);
208     g_return_if_fail(ruleset && ruleset->type == RULESET_STMT);
209     parse_tmp.stmtType = NORMAL_RULESET_STMT;
210     parse_tmp.currStmt = ruleset;
213 static void
214 end_selector_cb(CRDocHandler *a_handler,
215                 CRSelector *a_sel_list)
217     g_return_if_fail(a_handler && a_sel_list);
218     g_return_if_fail(a_handler->app_data != NULL);
219     ParseTmp &parse_tmp = *static_cast<ParseTmp *>(a_handler->app_data);
220     g_return_if_fail(parse_tmp.hasMagic());
221     CRStatement *const ruleset = parse_tmp.currStmt;
222     if (parse_tmp.stmtType == NORMAL_RULESET_STMT
223         && ruleset
224         && ruleset->type == RULESET_STMT
225         && ruleset->kind.ruleset->sel_list == a_sel_list)
226     {
227         parse_tmp.stylesheet->statements = cr_statement_append(parse_tmp.stylesheet->statements,
228                                                                ruleset);
229     } else {
230         g_warning("Found stmtType=%u, stmt=%p, stmt.type=%u, ruleset.sel_list=%p, a_sel_list=%p.",
231                   unsigned(parse_tmp.stmtType),
232                   ruleset,
233                   unsigned(ruleset->type),
234                   ruleset->kind.ruleset->sel_list,
235                   a_sel_list);
236     }
237     parse_tmp.currStmt = NULL;
238     parse_tmp.stmtType = NO_STMT;
241 static void
242 start_font_face_cb(CRDocHandler *a_handler,
243                    CRParsingLocation *)
245     g_return_if_fail(a_handler->app_data != NULL);
246     ParseTmp &parse_tmp = *static_cast<ParseTmp *>(a_handler->app_data);
247     g_return_if_fail(parse_tmp.hasMagic());
248     if (parse_tmp.stmtType != NO_STMT || parse_tmp.currStmt != NULL) {
249         g_warning("Expecting currStmt==NULL and stmtType==0 (NO_STMT) at start of @font-face, but found currStmt=%p, stmtType=%u",
250                   static_cast<void *>(parse_tmp.currStmt), unsigned(parse_tmp.stmtType));
251         // fixme: Check whether we need to unref currStmt if non-NULL.
252     }
253     parse_tmp.stmtType = FONT_FACE_STMT;
254     parse_tmp.currStmt = NULL;
257 static void
258 end_font_face_cb(CRDocHandler *a_handler)
260     g_return_if_fail(a_handler->app_data != NULL);
261     ParseTmp &parse_tmp = *static_cast<ParseTmp *>(a_handler->app_data);
262     g_return_if_fail(parse_tmp.hasMagic());
263     if (parse_tmp.stmtType != FONT_FACE_STMT || parse_tmp.currStmt != NULL) {
264         g_warning("Expecting currStmt==NULL and stmtType==1 (FONT_FACE_STMT) at end of @font-face, but found currStmt=%p, stmtType=%u",
265                   static_cast<void *>(parse_tmp.currStmt), unsigned(parse_tmp.stmtType));
266         // fixme: Check whether we need to unref currStmt if non-NULL.
267         parse_tmp.currStmt = NULL;
268     }
269     parse_tmp.stmtType = NO_STMT;
272 static void
273 property_cb(CRDocHandler *const a_handler,
274             CRString *const a_name,
275             CRTerm *const a_value, int const a_important)
277     g_return_if_fail(a_handler && a_name);
278     g_return_if_fail(a_handler->app_data != NULL);
279     ParseTmp &parse_tmp = *static_cast<ParseTmp *>(a_handler->app_data);
280     g_return_if_fail(parse_tmp.hasMagic());
281     if (parse_tmp.stmtType == FONT_FACE_STMT) {
282         if (parse_tmp.currStmt != NULL) {
283             g_warning("Found non-NULL currStmt %p though stmtType==FONT_FACE_STMT.", parse_tmp.currStmt);
284         }
285         /* We currently ignore @font-face descriptors. */
286         return;
287     }
288     CRStatement *const ruleset = parse_tmp.currStmt;
289     g_return_if_fail(ruleset
290                      && ruleset->type == RULESET_STMT
291                      && parse_tmp.stmtType == NORMAL_RULESET_STMT);
292     CRDeclaration *const decl = cr_declaration_new(ruleset, cr_string_dup(a_name), a_value);
293     g_return_if_fail(decl);
294     decl->important = a_important;
295     CRStatus const append_status = cr_statement_ruleset_append_decl(ruleset, decl);
296     g_return_if_fail(append_status == CR_OK);
299 static void
300 sp_style_elem_read_content(SPObject *const object)
302     SPStyleElem &style_elem = *SP_STYLE_ELEM(object);
304     /* fixme: If there's more than one <style> element in a document, then the document stylesheet
305      * will be set to a random one of them, even switching between them.
306      *
307      * However, I don't see in the spec what's supposed to happen when there are multiple <style>
308      * elements.  The wording suggests that <style>'s content should be a full stylesheet.
309      * http://www.w3.org/TR/REC-CSS2/cascade.html#cascade says that "The author specifies style
310      * sheets for a source document according to the conventions of the document language. For
311      * instance, in HTML, style sheets may be included in the document or linked externally."
312      * (Note the plural in both sentences.)  Whereas libcroco's CRCascade allows only one author
313      * stylesheet.  CRStyleSheet has no next/prev members that I can see, nor can I see any append
314      * stuff.
315      *
316      * Dodji replies "right, that's *bug*"; just an unexpected oversight.
317      */
319     GString *const text = concat_children(*style_elem.repr);
320     CRParser *parser = cr_parser_new_from_buf(reinterpret_cast<guchar *>(text->str), text->len,
321                                               CR_UTF_8, FALSE);
323     /* I see a cr_statement_parse_from_buf for returning a CRStatement*, but no corresponding
324        cr_stylesheet_parse_from_buf.  And cr_statement_parse_from_buf takes a char*, not a
325        CRInputBuf, and doesn't provide a way for calling it in a loop over the one buffer.
326        (I.e. it doesn't tell us where it got up to in the buffer.)
328        There's also the generic cr_parser_parse_stylesheet (or just cr_parser_parse), but that
329        just calls user-supplied callbacks rather than constructing a CRStylesheet.
330     */
331     CRDocHandler *sac_handler = cr_doc_handler_new();
332     // impl: ref_count inited to 0, so cr_parser_destroy suffices to delete sac_handler.
333     g_return_if_fail(sac_handler);  // out of memory
334     CRStyleSheet *const stylesheet = cr_stylesheet_new(NULL);
335     ParseTmp parse_tmp(stylesheet);
336     sac_handler->app_data = &parse_tmp;
337     sac_handler->start_selector = start_selector_cb;
338     sac_handler->end_selector = end_selector_cb;
339     sac_handler->start_font_face = start_font_face_cb;
340     sac_handler->end_font_face = end_font_face_cb;
341     sac_handler->property = property_cb;
342     /* todo: start_media, end_media. */
343     /* todo: Test error condition. */
344     cr_parser_set_sac_handler(parser, sac_handler);
345     CRStatus const parse_status = cr_parser_parse(parser);
346     g_assert(sac_handler->app_data == &parse_tmp);
347     if (parse_status == CR_OK) {
348         cr_cascade_set_sheet(style_elem.document->style_cascade, stylesheet, ORIGIN_AUTHOR);
349     } else {
350         if (parse_status != CR_PARSING_ERROR) {
351             g_printerr("parsing error code=%u\n", unsigned(parse_status));
352             /* Better than nothing.  TODO: Improve libcroco's error handling.  At a minimum, add a
353                strerror-like function so that we can give a string rather than an integer. */
354             /* TODO: Improve error diagnosis stuff in inkscape.  We'd like a panel showing the
355                errors/warnings/unsupported features of the current document. */
356         }
357     }
358     cr_parser_destroy(parser);
359     //object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
362 /**
363  * Does addListener(fns, data) on \a repr and all of its descendents.
364  */
365 static void
366 rec_add_listener(Inkscape::XML::Node &repr,
367                  Inkscape::XML::NodeEventVector const *const fns, void *const data)
369     repr.addListener(fns, data);
370     for (Inkscape::XML::Node *child = repr.firstChild(); child != NULL; child = child->next()) {
371         rec_add_listener(*child, fns, data);
372     }
375 static void
376 sp_style_elem_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
378     sp_style_elem_read_content(object);
380     sp_object_read_attr(object, "type");
381     sp_object_read_attr(object, "media");
383     static Inkscape::XML::NodeEventVector const nodeEventVector = {
384         child_add_rm_cb,   // child_added
385         child_add_rm_cb,   // child_removed
386         NULL,   // attr_changed
387         content_changed_cb,   // content_changed
388         child_order_changed_cb,   // order_changed
389     };
390     rec_add_listener(*repr, &nodeEventVector, object);
392     if (((SPObjectClass *) parent_class)->build) {
393         ((SPObjectClass *) parent_class)->build(object, document, repr);
394     }
398 /*
399   Local Variables:
400   mode:c++
401   c-file-style:"stroustrup"
402   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
403   indent-tabs-mode:nil
404   fill-column:99
405   End:
406 */
407 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :