1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /**
3 * @file
4 * Miscellaneous helpers for reprs.
5 */
6
7 /*
8 * Authors:
9 * Lauris Kaplinski <lauris@ximian.com>
10 * Jon A. Cruz <jon@joncruz.org>
11 *
12 * Copyright (C) 1999-2000 Lauris Kaplinski
13 * Copyright (C) 2000-2001 Ximian, Inc.
14 * g++ port Copyright (C) 2003 Nathan Hurst
15 *
16 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
17 */
18
19 #include <cstring>
20 #include <cstdlib>
21
22
23 #include <glib.h>
24 #include <glibmm.h>
25
26 #include <2geom/point.h>
27 #include "svg/stringstream.h"
28 #include "svg/css-ostringstream.h"
29 #include "svg/svg-length.h"
30
31 #include "xml/repr.h"
32 #include "xml/repr-sorting.h"
33
34
35 struct SPXMLNs {
36 SPXMLNs *next;
37 unsigned int uri, prefix;
38 };
39
40 /*#####################
41 # DEFINITIONS
42 #####################*/
43
44 #ifndef FALSE
45 # define FALSE 0
46 #endif
47
48 #ifndef TRUE
49 # define TRUE (!FALSE)
50 #endif
51
52 #ifndef MAX
53 # define MAX(a,b) (((a) < (b)) ? (b) : (a))
54 #endif
55
56 /*#####################
57 # FORWARD DECLARATIONS
58 #####################*/
59
60 static void sp_xml_ns_register_defaults();
61 static char *sp_xml_ns_auto_prefix(char const *uri);
62
63 /*#####################
64 # MAIN
65 #####################*/
66
67 /**
68 * SPXMLNs
69 */
70
71 static SPXMLNs *namespaces=nullptr;
72
73 /*
74 * There are the prefixes to use for the XML namespaces defined
75 * in repr.h
76 */
sp_xml_ns_register_defaults()77 static void sp_xml_ns_register_defaults()
78 {
79 static SPXMLNs defaults[11];
80
81 defaults[0].uri = g_quark_from_static_string(SP_SODIPODI_NS_URI);
82 defaults[0].prefix = g_quark_from_static_string("sodipodi");
83 defaults[0].next = &defaults[1];
84
85 defaults[1].uri = g_quark_from_static_string(SP_XLINK_NS_URI);
86 defaults[1].prefix = g_quark_from_static_string("xlink");
87 defaults[1].next = &defaults[2];
88
89 defaults[2].uri = g_quark_from_static_string(SP_SVG_NS_URI);
90 defaults[2].prefix = g_quark_from_static_string("svg");
91 defaults[2].next = &defaults[3];
92
93 defaults[3].uri = g_quark_from_static_string(SP_INKSCAPE_NS_URI);
94 defaults[3].prefix = g_quark_from_static_string("inkscape");
95 defaults[3].next = &defaults[4];
96
97 defaults[4].uri = g_quark_from_static_string(SP_RDF_NS_URI);
98 defaults[4].prefix = g_quark_from_static_string("rdf");
99 defaults[4].next = &defaults[5];
100
101 defaults[5].uri = g_quark_from_static_string(SP_CC_NS_URI);
102 defaults[5].prefix = g_quark_from_static_string("cc");
103 defaults[5].next = &defaults[6];
104
105 defaults[6].uri = g_quark_from_static_string(SP_DC_NS_URI);
106 defaults[6].prefix = g_quark_from_static_string("dc");
107 defaults[6].next = &defaults[8];
108
109 //defaults[7].uri = g_quark_from_static_string("https://inkscape.org/namespaces/deprecated/osb");
110 //defaults[7].prefix = g_quark_from_static_string("osb");
111 //defaults[7].next = &defaults[8];
112
113 // Inkscape versions prior to 0.44 would write this namespace
114 // URI instead of the correct sodipodi namespace; by adding this
115 // entry to the table last (where it gets used for URI -> prefix
116 // lookups, but not prefix -> URI lookups), we effectively transfer
117 // elements in this namespace to the correct sodipodi namespace:
118
119 defaults[8].uri = g_quark_from_static_string(SP_BROKEN_SODIPODI_NS_URI);
120 defaults[8].prefix = g_quark_from_static_string("sodipodi");
121 defaults[8].next = &defaults[9];
122
123 // "Duck prion"
124 // This URL became widespread due to a bug in versions <= 0.43
125
126 defaults[9].uri = g_quark_from_static_string("http://inkscape.sourceforge.net/DTD/s odipodi-0.dtd");
127 defaults[9].prefix = g_quark_from_static_string("sodipodi");
128 defaults[9].next = &defaults[10];
129
130 // This namespace URI is being phased out by Creative Commons
131
132 defaults[10].uri = g_quark_from_static_string(SP_OLD_CC_NS_URI);
133 defaults[10].prefix = g_quark_from_static_string("cc");
134 defaults[10].next = nullptr;
135
136 namespaces = &defaults[0];
137 }
138
sp_xml_ns_auto_prefix(char const * uri)139 char *sp_xml_ns_auto_prefix(char const *uri)
140 {
141 char const *start, *end;
142 char *new_prefix;
143 start = uri;
144 while ((end = strpbrk(start, ":/"))) {
145 start = end + 1;
146 }
147 end = start + strspn(start, "abcdefghijklmnopqrstuvwxyz");
148 if (end == start) {
149 start = "ns";
150 end = start + 2;
151 }
152 new_prefix = g_strndup(start, end - start);
153 if (sp_xml_ns_prefix_uri(new_prefix)) {
154 char *temp;
155 int counter=0;
156 do {
157 temp = g_strdup_printf("%s%d", new_prefix, counter++);
158 } while (sp_xml_ns_prefix_uri(temp));
159 g_free(new_prefix);
160 new_prefix = temp;
161 }
162 return new_prefix;
163 }
164
sp_xml_ns_uri_prefix(gchar const * uri,gchar const * suggested)165 gchar const *sp_xml_ns_uri_prefix(gchar const *uri, gchar const *suggested)
166 {
167 char const *prefix;
168
169 if (!uri) return nullptr;
170
171 if (!namespaces) {
172 sp_xml_ns_register_defaults();
173 }
174
175 GQuark const key = g_quark_from_string(uri);
176 prefix = nullptr;
177 for ( SPXMLNs *iter=namespaces ; iter ; iter = iter->next ) {
178 if ( iter->uri == key ) {
179 prefix = g_quark_to_string(iter->prefix);
180 break;
181 }
182 }
183
184 if (!prefix) {
185 char *new_prefix;
186 SPXMLNs *ns;
187 if (suggested) {
188 GQuark const prefix_key=g_quark_from_string(suggested);
189
190 SPXMLNs *found=namespaces;
191 while (found) {
192 if (found->prefix != prefix_key) {
193 found = found->next;
194 }
195 else {
196 break;
197 }
198 }
199
200 if (found) { // prefix already used?
201 new_prefix = sp_xml_ns_auto_prefix(uri);
202 } else { // safe to use suggested
203 new_prefix = g_strdup(suggested);
204 }
205 } else {
206 new_prefix = sp_xml_ns_auto_prefix(uri);
207 }
208
209 ns = g_new(SPXMLNs, 1);
210 g_assert( ns != nullptr );
211 ns->uri = g_quark_from_string(uri);
212 ns->prefix = g_quark_from_string(new_prefix);
213
214 g_free(new_prefix);
215
216 ns->next = namespaces;
217 namespaces = ns;
218
219 prefix = g_quark_to_string(ns->prefix);
220 }
221
222 return prefix;
223 }
224
sp_xml_ns_prefix_uri(gchar const * prefix)225 gchar const *sp_xml_ns_prefix_uri(gchar const *prefix)
226 {
227 SPXMLNs *iter;
228 char const *uri;
229
230 if (!prefix) return nullptr;
231
232 if (!namespaces) {
233 sp_xml_ns_register_defaults();
234 }
235
236 GQuark const key = g_quark_from_string(prefix);
237 uri = nullptr;
238 for ( iter = namespaces ; iter ; iter = iter->next ) {
239 if ( iter->prefix == key ) {
240 uri = g_quark_to_string(iter->uri);
241 break;
242 }
243 }
244 return uri;
245 }
246
247 /**
248 * Works for different-parent objects, so long as they have a common ancestor. Return value:
249 * 0 positions are equivalent
250 * 1 first object's position is greater than the second
251 * -1 first object's position is less than the second
252 * @todo Rewrite this function's description to be understandable
253 */
sp_repr_compare_position(Inkscape::XML::Node const * first,Inkscape::XML::Node const * second)254 int sp_repr_compare_position(Inkscape::XML::Node const *first, Inkscape::XML::Node const *second)
255 {
256 int p1, p2;
257 if (first->parent() == second->parent()) {
258 /* Basic case - first and second have same parent */
259 p1 = first->position();
260 p2 = second->position();
261 } else {
262 /* Special case - the two objects have different parents. They
263 could be in different groups or on different layers for
264 instance. */
265
266 // Find the lowest common ancestor(LCA)
267 Inkscape::XML::Node const *ancestor = LCA(first, second);
268 g_assert(ancestor != nullptr);
269
270 if (ancestor == first) {
271 return 1;
272 } else if (ancestor == second) {
273 return -1;
274 } else {
275 Inkscape::XML::Node const *to_first = AncetreFils(first, ancestor);
276 Inkscape::XML::Node const *to_second = AncetreFils(second, ancestor);
277 g_assert(to_second->parent() == to_first->parent());
278 p1 = to_first->position();
279 p2 = to_second->position();
280 }
281 }
282
283 if (p1 > p2) return 1;
284 if (p1 < p2) return -1;
285 return 0;
286
287 /* effic: Assuming that the parent--child relationship is consistent
288 (i.e. that the parent really does contain first and second among
289 its list of children), it should be equivalent to walk along the
290 children and see which we encounter first (returning 0 iff first
291 == second).
292
293 Given that this function is used solely for sorting, we can use a
294 similar approach to do the sort: gather the things to be sorted,
295 into an STL vector (to allow random access and faster
296 traversals). Do a single pass of the parent's children; for each
297 child, do a pass on whatever items in the vector we haven't yet
298 encountered. If the child is found, then swap it to the
299 beginning of the yet-unencountered elements of the vector.
300 Continue until no more than one remains unencountered. --
301 pjrm */
302 }
303
sp_repr_compare_position_bool(Inkscape::XML::Node const * first,Inkscape::XML::Node const * second)304 bool sp_repr_compare_position_bool(Inkscape::XML::Node const *first, Inkscape::XML::Node const *second){
305 return sp_repr_compare_position(first, second)<0;
306 }
307
308
309 /**
310 * Find an element node using an unique attribute.
311 *
312 * This function returns the first child of the specified node that has the attribute
313 * @c key equal to @c value. Note that this function does not recurse.
314 *
315 * @param repr The node to start from
316 * @param key The name of the attribute to use for comparisons
317 * @param value The value of the attribute to look for
318 * @relatesalso Inkscape::XML::Node
319 */
sp_repr_lookup_child(Inkscape::XML::Node * repr,gchar const * key,gchar const * value)320 Inkscape::XML::Node *sp_repr_lookup_child(Inkscape::XML::Node *repr,
321 gchar const *key,
322 gchar const *value)
323 {
324 g_return_val_if_fail(repr != nullptr, NULL);
325 for ( Inkscape::XML::Node *child = repr->firstChild() ; child ; child = child->next() ) {
326 gchar const *child_value = child->attribute(key);
327 if ( (child_value == value) ||
328 (value && child_value && !strcmp(child_value, value)) )
329 {
330 return child;
331 }
332 }
333 return nullptr;
334 }
335
336 /**
337 * Recursive version of sp_repr_lookup_child().
338 */
sp_repr_lookup_descendant(Inkscape::XML::Node const * repr,gchar const * key,gchar const * value)339 Inkscape::XML::Node const *sp_repr_lookup_descendant(Inkscape::XML::Node const *repr,
340 gchar const *key,
341 gchar const *value)
342 {
343 Inkscape::XML::Node const *found = nullptr;
344 g_return_val_if_fail(repr != nullptr, NULL);
345 gchar const *repr_value = repr->attribute(key);
346 if ( (repr_value == value) ||
347 (repr_value && value && strcmp(repr_value, value) == 0) ) {
348 found = repr;
349 } else {
350 for (Inkscape::XML::Node const *child = repr->firstChild() ; child && !found; child = child->next() ) {
351 found = sp_repr_lookup_descendant( child, key, value );
352 }
353 }
354 return found;
355 }
356
357
sp_repr_lookup_descendant(Inkscape::XML::Node * repr,gchar const * key,gchar const * value)358 Inkscape::XML::Node *sp_repr_lookup_descendant(Inkscape::XML::Node *repr,
359 gchar const *key,
360 gchar const *value)
361 {
362 Inkscape::XML::Node const *found = sp_repr_lookup_descendant( const_cast<Inkscape::XML::Node const *>(repr), key, value );
363 return const_cast<Inkscape::XML::Node *>(found);
364 }
365
sp_repr_lookup_name(Inkscape::XML::Node const * repr,gchar const * name,gint maxdepth)366 Inkscape::XML::Node const *sp_repr_lookup_name( Inkscape::XML::Node const *repr, gchar const *name, gint maxdepth )
367 {
368 Inkscape::XML::Node const *found = nullptr;
369 g_return_val_if_fail(repr != nullptr, NULL);
370 g_return_val_if_fail(name != nullptr, NULL);
371
372 GQuark const quark = g_quark_from_string(name);
373
374 if ( (GQuark)repr->code() == quark ) {
375 found = repr;
376 } else if ( maxdepth != 0 ) {
377 // maxdepth == -1 means unlimited
378 if ( maxdepth == -1 ) {
379 maxdepth = 0;
380 }
381
382 for (Inkscape::XML::Node const *child = repr->firstChild() ; child && !found; child = child->next() ) {
383 found = sp_repr_lookup_name( child, name, maxdepth - 1 );
384 }
385 }
386 return found;
387 }
388
sp_repr_lookup_name(Inkscape::XML::Node * repr,gchar const * name,gint maxdepth)389 Inkscape::XML::Node *sp_repr_lookup_name( Inkscape::XML::Node *repr, gchar const *name, gint maxdepth )
390 {
391 Inkscape::XML::Node const *found = sp_repr_lookup_name( const_cast<Inkscape::XML::Node const *>(repr), name, maxdepth );
392 return const_cast<Inkscape::XML::Node *>(found);
393 }
394
sp_repr_lookup_name_many(Inkscape::XML::Node const * repr,gchar const * name,gint maxdepth)395 std::vector<Inkscape::XML::Node const *> sp_repr_lookup_name_many( Inkscape::XML::Node const *repr, gchar const *name, gint maxdepth )
396 {
397 std::vector<Inkscape::XML::Node const *> nodes;
398 std::vector<Inkscape::XML::Node const *> found;
399 g_return_val_if_fail(repr != nullptr, nodes);
400 g_return_val_if_fail(name != nullptr, nodes);
401
402 GQuark const quark = g_quark_from_string(name);
403
404 if ( (GQuark)repr->code() == quark ) {
405 nodes.push_back(repr);
406 }
407
408 if ( maxdepth != 0 ) {
409 // maxdepth == -1 means unlimited
410 if ( maxdepth == -1 ) {
411 maxdepth = 0;
412 }
413
414 for (Inkscape::XML::Node const *child = repr->firstChild() ; child; child = child->next() ) {
415 found = sp_repr_lookup_name_many( child, name, maxdepth - 1);
416 nodes.insert(nodes.end(), found.begin(), found.end());
417 }
418 }
419
420 return nodes;
421 }
422
423 std::vector<Inkscape::XML::Node *>
sp_repr_lookup_property_many(Inkscape::XML::Node * repr,Glib::ustring const & property,Glib::ustring const & value,int maxdepth)424 sp_repr_lookup_property_many( Inkscape::XML::Node *repr, Glib::ustring const& property,
425 Glib::ustring const &value, int maxdepth )
426 {
427 std::vector<Inkscape::XML::Node *> nodes;
428 std::vector<Inkscape::XML::Node *> found;
429 g_return_val_if_fail(repr != nullptr, nodes);
430
431 SPCSSAttr* css = sp_repr_css_attr (repr, "style");
432 if (value == sp_repr_css_property (css, property, "")) {
433 nodes.push_back(repr);
434 }
435
436 if ( maxdepth != 0 ) {
437 // maxdepth == -1 means unlimited
438 if ( maxdepth == -1 ) {
439 maxdepth = 0;
440 }
441
442 for (Inkscape::XML::Node *child = repr->firstChild() ; child; child = child->next() ) {
443 found = sp_repr_lookup_property_many( child, property, value, maxdepth - 1);
444 nodes.insert(nodes.end(), found.begin(), found.end());
445 }
446 }
447
448 return nodes;
449 }
450
451 /**
452 * Determine if the node is a 'title', 'desc' or 'metadata' element.
453 */
sp_repr_is_meta_element(const Inkscape::XML::Node * node)454 bool sp_repr_is_meta_element(const Inkscape::XML::Node *node)
455 {
456 if (node == nullptr) return false;
457 if (node->type() != Inkscape::XML::NodeType::ELEMENT_NODE) return false;
458 gchar const *name = node->name();
459 if (name == nullptr) return false;
460 if (!std::strcmp(name, "svg:title")) return true;
461 if (!std::strcmp(name, "svg:desc")) return true;
462 if (!std::strcmp(name, "svg:metadata")) return true;
463 return false;
464 }
465
466 /**
467 * Parses the boolean value of an attribute "key" in repr and sets val accordingly, or to FALSE if
468 * the attr is not set.
469 *
470 * \return TRUE if the attr was set, FALSE otherwise.
471 */
sp_repr_get_boolean(Inkscape::XML::Node * repr,gchar const * key,unsigned int * val)472 unsigned int sp_repr_get_boolean(Inkscape::XML::Node *repr, gchar const *key, unsigned int *val)
473 {
474 gchar const *v;
475
476 g_return_val_if_fail(repr != nullptr, FALSE);
477 g_return_val_if_fail(key != nullptr, FALSE);
478 g_return_val_if_fail(val != nullptr, FALSE);
479
480 v = repr->attribute(key);
481
482 if (v != nullptr) {
483 if (!g_ascii_strcasecmp(v, "true") ||
484 !g_ascii_strcasecmp(v, "yes" ) ||
485 !g_ascii_strcasecmp(v, "y" ) ||
486 (atoi(v) != 0)) {
487 *val = TRUE;
488 } else {
489 *val = FALSE;
490 }
491 return TRUE;
492 } else {
493 *val = FALSE;
494 return FALSE;
495 }
496 }
497
sp_repr_get_int(Inkscape::XML::Node * repr,gchar const * key,int * val)498 unsigned int sp_repr_get_int(Inkscape::XML::Node *repr, gchar const *key, int *val)
499 {
500 gchar const *v;
501
502 g_return_val_if_fail(repr != nullptr, FALSE);
503 g_return_val_if_fail(key != nullptr, FALSE);
504 g_return_val_if_fail(val != nullptr, FALSE);
505
506 v = repr->attribute(key);
507
508 if (v != nullptr) {
509 *val = atoi(v);
510 return TRUE;
511 }
512
513 return FALSE;
514 }
515
sp_repr_get_double(Inkscape::XML::Node * repr,gchar const * key,double * val)516 unsigned int sp_repr_get_double(Inkscape::XML::Node *repr, gchar const *key, double *val)
517 {
518 g_return_val_if_fail(repr != nullptr, FALSE);
519 g_return_val_if_fail(key != nullptr, FALSE);
520 g_return_val_if_fail(val != nullptr, FALSE);
521
522 gchar const *v = repr->attribute(key);
523
524 if (v != nullptr) {
525 *val = g_ascii_strtod(v, nullptr);
526 return TRUE;
527 }
528
529 return FALSE;
530 }
531
sp_repr_set_boolean(Inkscape::XML::Node * repr,gchar const * key,unsigned int val)532 unsigned int sp_repr_set_boolean(Inkscape::XML::Node *repr, gchar const *key, unsigned int val)
533 {
534 g_return_val_if_fail(repr != nullptr, FALSE);
535 g_return_val_if_fail(key != nullptr, FALSE);
536
537 repr->setAttribute(key, (val) ? "true" : "false");
538 return true;
539 }
540
sp_repr_set_int(Inkscape::XML::Node * repr,gchar const * key,int val)541 unsigned int sp_repr_set_int(Inkscape::XML::Node *repr, gchar const *key, int val)
542 {
543 gchar c[32];
544
545 g_return_val_if_fail(repr != nullptr, FALSE);
546 g_return_val_if_fail(key != nullptr, FALSE);
547
548 g_snprintf(c, 32, "%d", val);
549
550 repr->setAttribute(key, c);
551 return true;
552 }
553
554 /**
555 * Set a property attribute to \a val [slightly rounded], in the format
556 * required for CSS properties: in particular, it never uses exponent
557 * notation.
558 */
sp_repr_set_css_double(Inkscape::XML::Node * repr,gchar const * key,double val)559 unsigned int sp_repr_set_css_double(Inkscape::XML::Node *repr, gchar const *key, double val)
560 {
561 g_return_val_if_fail(repr != nullptr, FALSE);
562 g_return_val_if_fail(key != nullptr, FALSE);
563
564 Inkscape::CSSOStringStream os;
565 os << val;
566
567 repr->setAttribute(key, os.str());
568 return true;
569 }
570
571 /**
572 * For attributes where an exponent is allowed.
573 *
574 * Not suitable for property attributes (fill-opacity, font-size etc.).
575 */
sp_repr_set_svg_double(Inkscape::XML::Node * repr,gchar const * key,double val)576 unsigned int sp_repr_set_svg_double(Inkscape::XML::Node *repr, gchar const *key, double val)
577 {
578 g_return_val_if_fail(repr != nullptr, FALSE);
579 g_return_val_if_fail(key != nullptr, FALSE);
580 g_return_val_if_fail(val==val, FALSE);//tests for nan
581
582 Inkscape::SVGOStringStream os;
583 os << val;
584
585 repr->setAttribute(key, os.str());
586 return true;
587 }
588
sp_repr_set_svg_non_default_double(Inkscape::XML::Node * repr,gchar const * key,double val,double default_value)589 unsigned int sp_repr_set_svg_non_default_double(Inkscape::XML::Node *repr, gchar const *key, double val, double default_value)
590 {
591 if (val==default_value){
592 repr->removeAttribute(key);
593 return true;
594 }
595 return sp_repr_set_svg_double(repr, key, val);
596 }
597
598 /**
599 * For attributes where an exponent is allowed.
600 *
601 * Not suitable for property attributes.
602 */
sp_repr_set_svg_length(Inkscape::XML::Node * repr,gchar const * key,SVGLength & val)603 unsigned int sp_repr_set_svg_length(Inkscape::XML::Node *repr, gchar const *key, SVGLength &val)
604 {
605 g_return_val_if_fail(repr != nullptr, FALSE);
606 g_return_val_if_fail(key != nullptr, FALSE);
607
608 repr->setAttribute(key, val.write());
609 return true;
610 }
611
sp_repr_set_point(Inkscape::XML::Node * repr,gchar const * key,Geom::Point const & val)612 unsigned sp_repr_set_point(Inkscape::XML::Node *repr, gchar const *key, Geom::Point const & val)
613 {
614 g_return_val_if_fail(repr != nullptr, FALSE);
615 g_return_val_if_fail(key != nullptr, FALSE);
616
617 Inkscape::SVGOStringStream os;
618 os << val[Geom::X] << "," << val[Geom::Y];
619
620 repr->setAttribute(key, os.str());
621 return true;
622 }
623
sp_repr_get_point(Inkscape::XML::Node * repr,gchar const * key,Geom::Point * val)624 unsigned int sp_repr_get_point(Inkscape::XML::Node *repr, gchar const *key, Geom::Point *val)
625 {
626 g_return_val_if_fail(repr != nullptr, FALSE);
627 g_return_val_if_fail(key != nullptr, FALSE);
628 g_return_val_if_fail(val != nullptr, FALSE);
629
630 gchar const *v = repr->attribute(key);
631
632 g_return_val_if_fail(v != nullptr, FALSE);
633
634 gchar ** strarray = g_strsplit(v, ",", 2);
635
636 if (strarray && strarray[0] && strarray[1]) {
637 double newx, newy;
638 newx = g_ascii_strtod(strarray[0], nullptr);
639 newy = g_ascii_strtod(strarray[1], nullptr);
640 g_strfreev (strarray);
641 *val = Geom::Point(newx, newy);
642 return TRUE;
643 }
644
645 g_strfreev (strarray);
646 return FALSE;
647 }
648
649 /*
650 Local Variables:
651 mode:c++
652 c-file-style:"stroustrup"
653 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
654 indent-tabs-mode:nil
655 fill-column:99
656 End:
657 */
658 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
659