Code

Merge and cleanup of GSoC C++-ification project.
[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 #include "style.h"
8 using Inkscape::XML::TEXT_NODE;
10 static void sp_style_elem_init(SPStyleElem *style_elem);
11 static void sp_style_elem_class_init(SPStyleElemClass *klass);
12 static void sp_style_elem_build(SPObject *object, SPDocument *doc, Inkscape::XML::Node *repr);
13 static void sp_style_elem_set(SPObject *object, unsigned const key, gchar const *const value);
14 static void sp_style_elem_read_content(SPObject *);
15 static Inkscape::XML::Node *sp_style_elem_write(SPObject *, Inkscape::XML::Document *, Inkscape::XML::Node *, guint flags);
17 static SPObjectClass *parent_class;
19 GType
20 sp_style_elem_get_type()
21 {
22     static GType type = 0;
23     if (!type) {
24         GTypeInfo info = {
25             sizeof(SPStyleElemClass),
26             NULL,   /* base_init */
27             NULL,   /* base_finalize */
28             (GClassInitFunc) sp_style_elem_class_init,
29             NULL,   /* class_finalize */
30             NULL,   /* class_data */
31             sizeof(SPStyleElem),
32             16,     /* n_preallocs */
33             (GInstanceInitFunc) sp_style_elem_init,
34             NULL,   /* value_table */
35         };
36         type = g_type_register_static(SP_TYPE_OBJECT, "SPStyleElem", &info, (GTypeFlags) 0);
37     }
39     return type;
40 }
42 static void
43 sp_style_elem_class_init(SPStyleElemClass *klass)
44 {
45     parent_class = (SPObjectClass *)g_type_class_ref(SP_TYPE_OBJECT);
46     /* FIXME */
48     klass->build = sp_style_elem_build;
49     klass->set = sp_style_elem_set;
50     klass->read_content = sp_style_elem_read_content;
51     klass->write = sp_style_elem_write;
52 }
54 static void
55 sp_style_elem_init(SPStyleElem *style_elem)
56 {
57     media_set_all(style_elem->media);
58     style_elem->is_css = false;
59 }
61 static void
62 sp_style_elem_set(SPObject *object, unsigned const key, gchar const *const value)
63 {
64     g_return_if_fail(object);
65     SPStyleElem &style_elem = *SP_STYLE_ELEM(object);
67     switch (key) {
68         case SP_ATTR_TYPE: {
69             if (!value) {
70                 /* TODO: `type' attribute is required.  Give error message as per
71                    http://www.w3.org/TR/SVG11/implnote.html#ErrorProcessing. */
72                 style_elem.is_css = false;
73             } else {
74                 /* fixme: determine what whitespace is allowed.  Will probably need to ask on SVG
75                  * list; though the relevant RFC may give info on its lexer. */
76                 style_elem.is_css = ( g_ascii_strncasecmp(value, "text/css", 8) == 0
77                                       && ( value[8] == '\0' ||
78                                            value[8] == ';'    ) );
79             }
80             break;
81         }
83 #if 0 /* unfinished */
84         case SP_ATTR_MEDIA: {
85             parse_media(style_elem, value);
86             break;
87         }
88 #endif
90         /* title is ignored. */
91         default: {
92             if (parent_class->set) {
93                 parent_class->set(object, key, value);
94             }
95             break;
96         }
97     }
98 }
100 static void
101 child_add_rm_cb(Inkscape::XML::Node *, Inkscape::XML::Node *, Inkscape::XML::Node *,
102                 void *const data)
104     sp_style_elem_read_content(static_cast<SPObject *>(data));
107 static void
108 content_changed_cb(Inkscape::XML::Node *, gchar const *, gchar const *,
109                    void *const data)
111     sp_style_elem_read_content(static_cast<SPObject *>(data));
114 static void
115 child_order_changed_cb(Inkscape::XML::Node *, Inkscape::XML::Node *,
116                        Inkscape::XML::Node *, Inkscape::XML::Node *,
117                        void *const data)
119     sp_style_elem_read_content(static_cast<SPObject *>(data));
122 static Inkscape::XML::Node *
123 sp_style_elem_write(SPObject *const object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint const flags)
125     if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
126         repr = xml_doc->createElement("svg:style");
127     }
129     g_return_val_if_fail(object, repr);
130     SPStyleElem &style_elem = *SP_STYLE_ELEM(object);
131     if (flags & SP_OBJECT_WRITE_BUILD) {
132         g_warning("nyi: Forming <style> content for SP_OBJECT_WRITE_BUILD.");
133         /* fixme: Consider having the CRStyleSheet be a member of SPStyleElem, and then
134            pretty-print to a string s, then repr->addChild(xml_doc->createTextNode(s), NULL). */
135     }
136     if (style_elem.is_css) {
137         repr->setAttribute("type", "text/css");
138     }
139     /* todo: media */
141     if (((SPObjectClass *) parent_class)->write)
142         ((SPObjectClass *) parent_class)->write(object, xml_doc, repr, flags);
144     return repr;
148 /** Returns the concatenation of the content of the text children of the specified object. */
149 static GString *
150 concat_children(Inkscape::XML::Node const &repr)
152     GString *ret = g_string_sized_new(0);
153     // effic: 0 is just to catch bugs.  Increase to something reasonable.
154     for (Inkscape::XML::Node const *rch = repr.firstChild(); rch != NULL; rch = rch->next()) {
155         if ( rch->type() == TEXT_NODE ) {
156             ret = g_string_append(ret, rch->content());
157         }
158     }
159     return ret;
164 /* Callbacks for SAC-style libcroco parser. */
166 enum StmtType { NO_STMT, FONT_FACE_STMT, NORMAL_RULESET_STMT };
168 struct ParseTmp
170     CRStyleSheet *const stylesheet;
171     StmtType stmtType;
172     CRStatement *currStmt;
173     unsigned magic;
174     static unsigned const ParseTmp_magic = 0x23474397;  // from /dev/urandom
176     ParseTmp(CRStyleSheet *const stylesheet) :
177         stylesheet(stylesheet),
178         stmtType(NO_STMT),
179         currStmt(NULL),
180         magic(ParseTmp_magic)
181     { }
183     bool hasMagic() const {
184         return magic == ParseTmp_magic;
185     }
187     ~ParseTmp()
188     {
189         g_return_if_fail(hasMagic());
190         magic = 0;
191     }
192 };
194 static void
195 start_selector_cb(CRDocHandler *a_handler,
196                   CRSelector *a_sel_list)
198     g_return_if_fail(a_handler && a_sel_list);
199     g_return_if_fail(a_handler->app_data != NULL);
200     ParseTmp &parse_tmp = *static_cast<ParseTmp *>(a_handler->app_data);
201     g_return_if_fail(parse_tmp.hasMagic());
202     if ( (parse_tmp.currStmt != NULL)
203          || (parse_tmp.stmtType != NO_STMT) ) {
204         g_warning("Expecting currStmt==NULL and stmtType==0 (NO_STMT) at start of ruleset, but found currStmt=%p, stmtType=%u",
205                   static_cast<void *>(parse_tmp.currStmt), unsigned(parse_tmp.stmtType));
206         // fixme: Check whether we need to unref currStmt if non-NULL.
207     }
208     CRStatement *ruleset = cr_statement_new_ruleset(parse_tmp.stylesheet, a_sel_list, NULL, NULL);
209     g_return_if_fail(ruleset && ruleset->type == RULESET_STMT);
210     parse_tmp.stmtType = NORMAL_RULESET_STMT;
211     parse_tmp.currStmt = ruleset;
214 static void
215 end_selector_cb(CRDocHandler *a_handler,
216                 CRSelector *a_sel_list)
218     g_return_if_fail(a_handler && a_sel_list);
219     g_return_if_fail(a_handler->app_data != NULL);
220     ParseTmp &parse_tmp = *static_cast<ParseTmp *>(a_handler->app_data);
221     g_return_if_fail(parse_tmp.hasMagic());
222     CRStatement *const ruleset = parse_tmp.currStmt;
223     if (parse_tmp.stmtType == NORMAL_RULESET_STMT
224         && ruleset
225         && ruleset->type == RULESET_STMT
226         && ruleset->kind.ruleset->sel_list == a_sel_list)
227     {
228         parse_tmp.stylesheet->statements = cr_statement_append(parse_tmp.stylesheet->statements,
229                                                                ruleset);
230     } else {
231         g_warning("Found stmtType=%u, stmt=%p, stmt.type=%u, ruleset.sel_list=%p, a_sel_list=%p.",
232                   unsigned(parse_tmp.stmtType),
233                   ruleset,
234                   unsigned(ruleset->type),
235                   ruleset->kind.ruleset->sel_list,
236                   a_sel_list);
237     }
238     parse_tmp.currStmt = NULL;
239     parse_tmp.stmtType = NO_STMT;
242 static void
243 start_font_face_cb(CRDocHandler *a_handler,
244                    CRParsingLocation *)
246     g_return_if_fail(a_handler->app_data != NULL);
247     ParseTmp &parse_tmp = *static_cast<ParseTmp *>(a_handler->app_data);
248     g_return_if_fail(parse_tmp.hasMagic());
249     if (parse_tmp.stmtType != NO_STMT || parse_tmp.currStmt != NULL) {
250         g_warning("Expecting currStmt==NULL and stmtType==0 (NO_STMT) at start of @font-face, but found currStmt=%p, stmtType=%u",
251                   static_cast<void *>(parse_tmp.currStmt), unsigned(parse_tmp.stmtType));
252         // fixme: Check whether we need to unref currStmt if non-NULL.
253     }
254     parse_tmp.stmtType = FONT_FACE_STMT;
255     parse_tmp.currStmt = NULL;
258 static void
259 end_font_face_cb(CRDocHandler *a_handler)
261     g_return_if_fail(a_handler->app_data != NULL);
262     ParseTmp &parse_tmp = *static_cast<ParseTmp *>(a_handler->app_data);
263     g_return_if_fail(parse_tmp.hasMagic());
264     if (parse_tmp.stmtType != FONT_FACE_STMT || parse_tmp.currStmt != NULL) {
265         g_warning("Expecting currStmt==NULL and stmtType==1 (FONT_FACE_STMT) at end of @font-face, but found currStmt=%p, stmtType=%u",
266                   static_cast<void *>(parse_tmp.currStmt), unsigned(parse_tmp.stmtType));
267         // fixme: Check whether we need to unref currStmt if non-NULL.
268         parse_tmp.currStmt = NULL;
269     }
270     parse_tmp.stmtType = NO_STMT;
273 static void
274 property_cb(CRDocHandler *const a_handler,
275             CRString *const a_name,
276             CRTerm *const a_value, gboolean const a_important)
278     g_return_if_fail(a_handler && a_name);
279     g_return_if_fail(a_handler->app_data != NULL);
280     ParseTmp &parse_tmp = *static_cast<ParseTmp *>(a_handler->app_data);
281     g_return_if_fail(parse_tmp.hasMagic());
282     if (parse_tmp.stmtType == FONT_FACE_STMT) {
283         if (parse_tmp.currStmt != NULL) {
284             g_warning("Found non-NULL currStmt %p though stmtType==FONT_FACE_STMT.", parse_tmp.currStmt);
285         }
286         /* We currently ignore @font-face descriptors. */
287         return;
288     }
289     CRStatement *const ruleset = parse_tmp.currStmt;
290     g_return_if_fail(ruleset
291                      && ruleset->type == RULESET_STMT
292                      && parse_tmp.stmtType == NORMAL_RULESET_STMT);
293     CRDeclaration *const decl = cr_declaration_new(ruleset, cr_string_dup(a_name), a_value);
294     g_return_if_fail(decl);
295     decl->important = a_important;
296     CRStatus const append_status = cr_statement_ruleset_append_decl(ruleset, decl);
297     g_return_if_fail(append_status == CR_OK);
300 static void
301 sp_style_elem_read_content(SPObject *const object)
303     SPStyleElem &style_elem = *SP_STYLE_ELEM(object);
305     /* fixme: If there's more than one <style> element in a document, then the document stylesheet
306      * will be set to a random one of them, even switching between them.
307      *
308      * However, I don't see in the spec what's supposed to happen when there are multiple <style>
309      * elements.  The wording suggests that <style>'s content should be a full stylesheet.
310      * http://www.w3.org/TR/REC-CSS2/cascade.html#cascade says that "The author specifies style
311      * sheets for a source document according to the conventions of the document language. For
312      * instance, in HTML, style sheets may be included in the document or linked externally."
313      * (Note the plural in both sentences.)  Whereas libcroco's CRCascade allows only one author
314      * stylesheet.  CRStyleSheet has no next/prev members that I can see, nor can I see any append
315      * stuff.
316      *
317      * Dodji replies "right, that's *bug*"; just an unexpected oversight.
318      */
320     //XML Tree being used directly here while it shouldn't be.
321     GString *const text = concat_children(*style_elem.getRepr());
322     CRParser *parser = cr_parser_new_from_buf(reinterpret_cast<guchar *>(text->str), text->len,
323                                               CR_UTF_8, FALSE);
325     /* I see a cr_statement_parse_from_buf for returning a CRStatement*, but no corresponding
326        cr_stylesheet_parse_from_buf.  And cr_statement_parse_from_buf takes a char*, not a
327        CRInputBuf, and doesn't provide a way for calling it in a loop over the one buffer.
328        (I.e. it doesn't tell us where it got up to in the buffer.)
330        There's also the generic cr_parser_parse_stylesheet (or just cr_parser_parse), but that
331        just calls user-supplied callbacks rather than constructing a CRStylesheet.
332     */
333     CRDocHandler *sac_handler = cr_doc_handler_new();
334     // impl: ref_count inited to 0, so cr_parser_destroy suffices to delete sac_handler.
335     g_return_if_fail(sac_handler);  // out of memory
336     CRStyleSheet *const stylesheet = cr_stylesheet_new(NULL);
337     ParseTmp parse_tmp(stylesheet);
338     sac_handler->app_data = &parse_tmp;
339     sac_handler->start_selector = start_selector_cb;
340     sac_handler->end_selector = end_selector_cb;
341     sac_handler->start_font_face = start_font_face_cb;
342     sac_handler->end_font_face = end_font_face_cb;
343     sac_handler->property = property_cb;
344     /* todo: start_media, end_media. */
345     /* todo: Test error condition. */
346     cr_parser_set_sac_handler(parser, sac_handler);
347     CRStatus const parse_status = cr_parser_parse(parser);
348     g_assert(sac_handler->app_data == &parse_tmp);
349     if (parse_status == CR_OK) {
350         cr_cascade_set_sheet(style_elem.document->style_cascade, stylesheet, ORIGIN_AUTHOR);
351     } else {
352         if (parse_status != CR_PARSING_ERROR) {
353             g_printerr("parsing error code=%u\n", unsigned(parse_status));
354             /* Better than nothing.  TODO: Improve libcroco's error handling.  At a minimum, add a
355                strerror-like function so that we can give a string rather than an integer. */
356             /* TODO: Improve error diagnosis stuff in inkscape.  We'd like a panel showing the
357                errors/warnings/unsupported features of the current document. */
358         }
359     }
360     cr_parser_destroy(parser);
361     //object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
363     // Style references via class= do not, and actually cannot, use autoupdating URIReferences.
364     // Therefore, if an object refers to a stylesheet which has not yet loaded when the object is being loaded
365     // (e.g. if the stylesheet is below or inside the object in XML), its class= has no effect (bug 1491639).
366     // Below is a partial hack that fixes this for a single case: when the <style> is a child of the object
367     // that uses a style from it. It just forces the parent of <style> to reread its style as soon as the stylesheet
368     // is fully loaded. Naturally, this won't work if the user of the stylesheet is its grandparent or precedent.
369     SPObject *parent = SP_OBJECT_PARENT (object);
370     if ( parent ) {
371         sp_style_read_from_object(parent->style, parent);
372     }
375 /**
376  * Does addListener(fns, data) on \a repr and all of its descendents.
377  */
378 static void
379 rec_add_listener(Inkscape::XML::Node &repr,
380                  Inkscape::XML::NodeEventVector const *const fns, void *const data)
382     repr.addListener(fns, data);
383     for (Inkscape::XML::Node *child = repr.firstChild(); child != NULL; child = child->next()) {
384         rec_add_listener(*child, fns, data);
385     }
388 static void
389 sp_style_elem_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
391     sp_style_elem_read_content(object);
393     object->readAttr( "type" );
394     object->readAttr( "media" );
396     static Inkscape::XML::NodeEventVector const nodeEventVector = {
397         child_add_rm_cb,   // child_added
398         child_add_rm_cb,   // child_removed
399         NULL,   // attr_changed
400         content_changed_cb,   // content_changed
401         child_order_changed_cb,   // order_changed
402     };
403     rec_add_listener(*repr, &nodeEventVector, object);
405     if (((SPObjectClass *) parent_class)->build) {
406         ((SPObjectClass *) parent_class)->build(object, document, repr);
407     }
411 /*
412   Local Variables:
413   mode:c++
414   c-file-style:"stroustrup"
415   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
416   indent-tabs-mode:nil
417   fill-column:99
418   End:
419 */
420 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :