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::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)
103 {
104 sp_style_elem_read_content(static_cast<SPObject *>(data));
105 }
107 static void
108 content_changed_cb(Inkscape::XML::Node *, gchar const *, gchar const *,
109 void *const data)
110 {
111 sp_style_elem_read_content(static_cast<SPObject *>(data));
112 }
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)
118 {
119 sp_style_elem_read_content(static_cast<SPObject *>(data));
120 }
122 static Inkscape::XML::Node *
123 sp_style_elem_write(SPObject *const object, Inkscape::XML::Node *repr, guint const flags)
124 {
125 if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
126 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(object));
127 repr = xml_doc->createElement("svg:style");
128 }
130 g_return_val_if_fail(object, repr);
131 SPStyleElem &style_elem = *SP_STYLE_ELEM(object);
132 if (flags & SP_OBJECT_WRITE_BUILD) {
133 g_warning("nyi: Forming <style> content for SP_OBJECT_WRITE_BUILD.");
134 /* fixme: Consider having the CRStyleSheet be a member of SPStyleElem, and then
135 pretty-print to a string s, then repr->addChild(xml_doc->createTextNode(s), NULL). */
136 }
137 if (style_elem.is_css) {
138 repr->setAttribute("type", "text/css");
139 }
140 /* todo: media */
142 if (((SPObjectClass *) parent_class)->write)
143 ((SPObjectClass *) parent_class)->write(object, repr, flags);
145 return repr;
146 }
149 /** Returns the concatenation of the content of the text children of the specified object. */
150 static GString *
151 concat_children(Inkscape::XML::Node const &repr)
152 {
153 GString *ret = g_string_sized_new(0);
154 // effic: 0 is just to catch bugs. Increase to something reasonable.
155 for (Inkscape::XML::Node const *rch = repr.firstChild(); rch != NULL; rch = rch->next()) {
156 if ( rch->type() == TEXT_NODE ) {
157 ret = g_string_append(ret, rch->content());
158 }
159 }
160 return ret;
161 }
165 /* Callbacks for SAC-style libcroco parser. */
167 enum StmtType { NO_STMT, FONT_FACE_STMT, NORMAL_RULESET_STMT };
169 struct ParseTmp
170 {
171 CRStyleSheet *const stylesheet;
172 StmtType stmtType;
173 CRStatement *currStmt;
174 unsigned magic;
175 static unsigned const ParseTmp_magic = 0x23474397; // from /dev/urandom
177 ParseTmp(CRStyleSheet *const stylesheet) :
178 stylesheet(stylesheet),
179 stmtType(NO_STMT),
180 currStmt(NULL),
181 magic(ParseTmp_magic)
182 { }
184 bool hasMagic() const {
185 return magic == ParseTmp_magic;
186 }
188 ~ParseTmp()
189 {
190 g_return_if_fail(hasMagic());
191 magic = 0;
192 }
193 };
195 static void
196 start_selector_cb(CRDocHandler *a_handler,
197 CRSelector *a_sel_list)
198 {
199 g_return_if_fail(a_handler && a_sel_list);
200 g_return_if_fail(a_handler->app_data != NULL);
201 ParseTmp &parse_tmp = *static_cast<ParseTmp *>(a_handler->app_data);
202 g_return_if_fail(parse_tmp.hasMagic());
203 if ( (parse_tmp.currStmt != NULL)
204 || (parse_tmp.stmtType != NO_STMT) ) {
205 g_warning("Expecting currStmt==NULL and stmtType==0 (NO_STMT) at start of ruleset, but found currStmt=%p, stmtType=%u",
206 static_cast<void *>(parse_tmp.currStmt), unsigned(parse_tmp.stmtType));
207 // fixme: Check whether we need to unref currStmt if non-NULL.
208 }
209 CRStatement *ruleset = cr_statement_new_ruleset(parse_tmp.stylesheet, a_sel_list, NULL, NULL);
210 g_return_if_fail(ruleset && ruleset->type == RULESET_STMT);
211 parse_tmp.stmtType = NORMAL_RULESET_STMT;
212 parse_tmp.currStmt = ruleset;
213 }
215 static void
216 end_selector_cb(CRDocHandler *a_handler,
217 CRSelector *a_sel_list)
218 {
219 g_return_if_fail(a_handler && a_sel_list);
220 g_return_if_fail(a_handler->app_data != NULL);
221 ParseTmp &parse_tmp = *static_cast<ParseTmp *>(a_handler->app_data);
222 g_return_if_fail(parse_tmp.hasMagic());
223 CRStatement *const ruleset = parse_tmp.currStmt;
224 if (parse_tmp.stmtType == NORMAL_RULESET_STMT
225 && ruleset
226 && ruleset->type == RULESET_STMT
227 && ruleset->kind.ruleset->sel_list == a_sel_list)
228 {
229 parse_tmp.stylesheet->statements = cr_statement_append(parse_tmp.stylesheet->statements,
230 ruleset);
231 } else {
232 g_warning("Found stmtType=%u, stmt=%p, stmt.type=%u, ruleset.sel_list=%p, a_sel_list=%p.",
233 unsigned(parse_tmp.stmtType),
234 ruleset,
235 unsigned(ruleset->type),
236 ruleset->kind.ruleset->sel_list,
237 a_sel_list);
238 }
239 parse_tmp.currStmt = NULL;
240 parse_tmp.stmtType = NO_STMT;
241 }
243 static void
244 start_font_face_cb(CRDocHandler *a_handler,
245 CRParsingLocation *)
246 {
247 g_return_if_fail(a_handler->app_data != NULL);
248 ParseTmp &parse_tmp = *static_cast<ParseTmp *>(a_handler->app_data);
249 g_return_if_fail(parse_tmp.hasMagic());
250 if (parse_tmp.stmtType != NO_STMT || parse_tmp.currStmt != NULL) {
251 g_warning("Expecting currStmt==NULL and stmtType==0 (NO_STMT) at start of @font-face, but found currStmt=%p, stmtType=%u",
252 static_cast<void *>(parse_tmp.currStmt), unsigned(parse_tmp.stmtType));
253 // fixme: Check whether we need to unref currStmt if non-NULL.
254 }
255 parse_tmp.stmtType = FONT_FACE_STMT;
256 parse_tmp.currStmt = NULL;
257 }
259 static void
260 end_font_face_cb(CRDocHandler *a_handler)
261 {
262 g_return_if_fail(a_handler->app_data != NULL);
263 ParseTmp &parse_tmp = *static_cast<ParseTmp *>(a_handler->app_data);
264 g_return_if_fail(parse_tmp.hasMagic());
265 if (parse_tmp.stmtType != FONT_FACE_STMT || parse_tmp.currStmt != NULL) {
266 g_warning("Expecting currStmt==NULL and stmtType==1 (FONT_FACE_STMT) at end of @font-face, but found currStmt=%p, stmtType=%u",
267 static_cast<void *>(parse_tmp.currStmt), unsigned(parse_tmp.stmtType));
268 // fixme: Check whether we need to unref currStmt if non-NULL.
269 parse_tmp.currStmt = NULL;
270 }
271 parse_tmp.stmtType = NO_STMT;
272 }
274 static void
275 property_cb(CRDocHandler *const a_handler,
276 CRString *const a_name,
277 CRTerm *const a_value, gboolean const a_important)
278 {
279 g_return_if_fail(a_handler && a_name);
280 g_return_if_fail(a_handler->app_data != NULL);
281 ParseTmp &parse_tmp = *static_cast<ParseTmp *>(a_handler->app_data);
282 g_return_if_fail(parse_tmp.hasMagic());
283 if (parse_tmp.stmtType == FONT_FACE_STMT) {
284 if (parse_tmp.currStmt != NULL) {
285 g_warning("Found non-NULL currStmt %p though stmtType==FONT_FACE_STMT.", parse_tmp.currStmt);
286 }
287 /* We currently ignore @font-face descriptors. */
288 return;
289 }
290 CRStatement *const ruleset = parse_tmp.currStmt;
291 g_return_if_fail(ruleset
292 && ruleset->type == RULESET_STMT
293 && parse_tmp.stmtType == NORMAL_RULESET_STMT);
294 CRDeclaration *const decl = cr_declaration_new(ruleset, cr_string_dup(a_name), a_value);
295 g_return_if_fail(decl);
296 decl->important = a_important;
297 CRStatus const append_status = cr_statement_ruleset_append_decl(ruleset, decl);
298 g_return_if_fail(append_status == CR_OK);
299 }
301 static void
302 sp_style_elem_read_content(SPObject *const object)
303 {
304 SPStyleElem &style_elem = *SP_STYLE_ELEM(object);
306 /* fixme: If there's more than one <style> element in a document, then the document stylesheet
307 * will be set to a random one of them, even switching between them.
308 *
309 * However, I don't see in the spec what's supposed to happen when there are multiple <style>
310 * elements. The wording suggests that <style>'s content should be a full stylesheet.
311 * http://www.w3.org/TR/REC-CSS2/cascade.html#cascade says that "The author specifies style
312 * sheets for a source document according to the conventions of the document language. For
313 * instance, in HTML, style sheets may be included in the document or linked externally."
314 * (Note the plural in both sentences.) Whereas libcroco's CRCascade allows only one author
315 * stylesheet. CRStyleSheet has no next/prev members that I can see, nor can I see any append
316 * stuff.
317 *
318 * Dodji replies "right, that's *bug*"; just an unexpected oversight.
319 */
321 GString *const text = concat_children(*style_elem.repr);
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 sp_style_read_from_object(parent->style, parent);
371 }
373 /**
374 * Does addListener(fns, data) on \a repr and all of its descendents.
375 */
376 static void
377 rec_add_listener(Inkscape::XML::Node &repr,
378 Inkscape::XML::NodeEventVector const *const fns, void *const data)
379 {
380 repr.addListener(fns, data);
381 for (Inkscape::XML::Node *child = repr.firstChild(); child != NULL; child = child->next()) {
382 rec_add_listener(*child, fns, data);
383 }
384 }
386 static void
387 sp_style_elem_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
388 {
389 sp_style_elem_read_content(object);
391 sp_object_read_attr(object, "type");
392 sp_object_read_attr(object, "media");
394 static Inkscape::XML::NodeEventVector const nodeEventVector = {
395 child_add_rm_cb, // child_added
396 child_add_rm_cb, // child_removed
397 NULL, // attr_changed
398 content_changed_cb, // content_changed
399 child_order_changed_cb, // order_changed
400 };
401 rec_add_listener(*repr, &nodeEventVector, object);
403 if (((SPObjectClass *) parent_class)->build) {
404 ((SPObjectClass *) parent_class)->build(object, document, repr);
405 }
406 }
409 /*
410 Local Variables:
411 mode:c++
412 c-file-style:"stroustrup"
413 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
414 indent-tabs-mode:nil
415 fill-column:99
416 End:
417 */
418 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :