1 // -*- C++ -*-
2 
3 /*
4  * CDXML files loader plugin
5  * cdxml.cc
6  *
7  * Copyright (C) 2007-2016 Jean Bréfort <jean.brefort@normalesup.org>
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License as
11  * published by the Free Software Foundation; either version 3 of the
12  * License, or (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
22  * USA
23  */
24 
25 #include "config.h"
26 #include <gcp/document.h>
27 #include <gcp/theme.h>
28 #include <gcp/view.h>
29 #include <gcp/widgetdata.h>
30 #include <gcu/application.h>
31 #include <gcu/atom.h>
32 #include <gcu/bond.h>
33 #include <gcu/element.h>
34 #include <gcu/formula.h>
35 #include <gcu/loader.h>
36 #include <gcu/molecule.h>
37 #include <gcu/objprops.h>
38 #include <gcu/xml-utils.h>
39 
40 #include <goffice/app/module-plugin-defs.h>
41 #include <gsf/gsf-libxml.h>
42 #include <glib/gi18n-lib.h>
43 #include <libintl.h>
44 #include <cstdio>
45 #include <list>
46 #include <map>
47 #include <stack>
48 #include <string>
49 #include <vector>
50 #include <cstring>
51 
52 #include <iostream>
53 #include <sstream>
54 
55 using namespace std;
56 using namespace gcu;
57 
58 static map<string, unsigned> KnownProps;
59 
60 typedef struct {
61 	unsigned index;
62 	string encoding;
63 	string name;
64 } CDXMLFont;
65 
66 typedef struct {
67 	xmlDocPtr xml;
68 	xmlNodePtr node;
69 	xmlNodePtr parent;
70 	GOIOContext *s;
71 	bool italic;
72 	bool bold;
73 	bool underline;
74 	unsigned font;
75 	double size;
76 	int position;
77 	unsigned color;
78 } WriteTextState;
79 
80 class CDXMLLoader: public gcu::Loader
81 {
82 public:
83 	CDXMLLoader ();
84 	virtual ~CDXMLLoader ();
85 
86 	ContentType Read (Document *doc, GsfInput *in, char const *mime_type, GOIOContext *io);
87 	bool Write (Object const *obj, GsfOutput *out, char const *mime_type, GOIOContext *io, ContentType type);
88 
89 private:
90 	bool WriteObject (xmlDocPtr xml, xmlNodePtr node, Object const *object, GOIOContext *io);
91 	static void AddIntProperty (xmlNodePtr node, char const *id, int value);
92 	static void AddFloatProperty (xmlNodePtr node, char const *id, double value);
93 	static void AddStringProperty (xmlNodePtr node, char const *id, string const &value);
94 	static bool WriteArrow (CDXMLLoader *loader, xmlDocPtr xml, xmlNodePtr parent, Object const *obj, GOIOContext *s);
95 	static bool WriteAtom (CDXMLLoader *loader, xmlDocPtr xml, xmlNodePtr parent, Object const *obj, GOIOContext *s);
96 	static bool WriteFragment (CDXMLLoader *loader, xmlDocPtr xml, xmlNodePtr parent, Object const *obj, GOIOContext *s);
97 	static bool WriteBond (CDXMLLoader *loader, xmlDocPtr xml, xmlNodePtr parent, Object const *obj, GOIOContext *s);
98 	static bool WriteMesomery(CDXMLLoader *loader, xmlDocPtr xml, xmlNodePtr parent, Object const *obj, GOIOContext *s);
99 	static bool WriteMolecule (CDXMLLoader *loader, xmlDocPtr xml, xmlNodePtr parent, Object const *obj, GOIOContext *s);
100 	static bool WriteReaction(CDXMLLoader *loader, xmlDocPtr xml, xmlNodePtr parent, Object const *obj, GOIOContext *s);
101 	static bool WriteReactionStep(CDXMLLoader *loader, xmlDocPtr xml, xmlNodePtr parent, Object const *obj, GOIOContext *s);
102 	static bool WriteRetrosynthesis(CDXMLLoader *loader, xmlDocPtr xml, xmlNodePtr parent, Object const *obj, GOIOContext *s);
103 	static bool WriteText(CDXMLLoader *loader, xmlDocPtr xml, xmlNodePtr parent, Object const *obj, GOIOContext *s);
104 	bool WriteNode (xmlNodePtr node, WriteTextState *state);
105 	bool WriteScheme (xmlDocPtr xml, xmlNodePtr parent, Object const *obj, std::string const &arrow_type, GOIOContext *s);
106 
107 private:
108 	map <string, bool (*) (CDXMLLoader *, xmlDocPtr, xmlNodePtr, Object const *, GOIOContext *)> m_WriteCallbacks;
109 	map <unsigned, GOColor> m_Colors;
110 	map <unsigned, CDXMLFont> m_Fonts;
111 	map <string, unsigned> m_SavedIds;
112 	int m_MaxId;
113 	unsigned m_Z;
114 	int m_LabelFont, m_Font;
115 	unsigned m_LabelFontFace, m_LabelFontColor;
116 	double m_FontSize, m_LabelFontSize, m_Scale, m_Zoom, m_CHeight;
117 	bool m_WriteScheme;
118 };
119 
CDXMLLoader()120 CDXMLLoader::CDXMLLoader ()
121 {
122 	AddMimeType ("chemical/x-cdxml");
123 	KnownProps["Comment"] = GCU_PROP_DOC_COMMENT;
124 	KnownProps["CreationDate"] = GCU_PROP_DOC_CREATION_TIME;
125 	KnownProps["CreationUserName"] = GCU_PROP_DOC_CREATOR;
126 	KnownProps["ModificationDate"] = GCU_PROP_DOC_MODIFICATION_TIME;
127 	KnownProps["Name"] = GCU_PROP_DOC_TITLE;
128 	KnownProps["p"] = GCU_PROP_POS2D;
129 	KnownProps["Element"] = GCU_PROP_ATOM_Z;
130 	KnownProps["Charge"] = GCU_PROP_ATOM_CHARGE;
131 	KnownProps["id"] = GCU_PROP_ID;
132 	KnownProps["B"] = GCU_PROP_BOND_BEGIN;
133 	KnownProps["Display"] = GCU_PROP_BOND_TYPE;
134 	KnownProps["E"] = GCU_PROP_BOND_END;
135 	KnownProps["Order"] = GCU_PROP_BOND_ORDER;
136 	KnownProps["DoublePosition"] = GCU_PROP_BOND_DOUBLE_POSITION;
137 	KnownProps["LabelJustification"] =GCU_PROP_TEXT_JUSTIFICATION;
138 	KnownProps["CaptionJustification"] =GCU_PROP_TEXT_ALIGNMENT;
139 	KnownProps["LabelAlignment"] = GCU_PROP_TEXT_ALIGNMENT;
140 	KnownProps["Justification"] =GCU_PROP_TEXT_JUSTIFICATION;
141 	// Add write callbacks
142 	m_WriteCallbacks["atom"] = WriteAtom;
143 	m_WriteCallbacks["fragment"] = WriteFragment;
144 	m_WriteCallbacks["bond"] = WriteBond;
145 	m_WriteCallbacks["molecule"] = WriteMolecule;
146 	m_WriteCallbacks["text"] = WriteText;
147 	m_WriteCallbacks["reaction"] = WriteReaction;
148 	m_WriteCallbacks["reaction-arrow"] = WriteArrow;
149 	m_WriteCallbacks["mesomery"] = WriteMesomery;
150 	m_WriteCallbacks["mesomery-arrow"] = WriteArrow;
151 	m_WriteCallbacks["retrosynthesis-arrow"] = WriteArrow;
152 	m_WriteCallbacks["retrosynthesis"] = WriteRetrosynthesis;
153 	m_WriteScheme = true;
154 }
155 
~CDXMLLoader()156 CDXMLLoader::~CDXMLLoader ()
157 {
158 	RemoveMimeType ("chemical/x-cdxml");
159 }
160 
161 typedef struct {
162 	Object *obj;
163 	unsigned property;
164 	string value;
165 } CDXMLProps;
166 
167 typedef struct {
168 	std::list < unsigned > Arrows, Reagents, Products, ObjectsAbove, ObjectsBelow;
169 } StepData;
170 
171 typedef struct {
172 	unsigned Id;
173 	std::list < StepData > Steps;
174 } SchemeData;
175 
176 typedef struct {
177 	Document *doc;
178 	Application *app;
179 	gcp::Theme *theme;
180 	ostringstream themedesc;
181 	GOIOContext *context;
182 	stack<Object*> cur;
183 	list<CDXMLProps> failed;
184 	map<unsigned, CDXMLFont> fonts;
185 	std::map <unsigned, std::string> loaded_ids;
186 	vector<string> colors;
187 	string markup;
188 	unsigned attributes;
189 	unsigned font;
190 	unsigned color;
191 	string size;
192 	int line_height;
193 	unsigned captionFont, labelFont, textAlign;
194 	double CHeight, padding;
195 	SchemeData scheme;
196 	std::list < SchemeData > schemes;
197 } CDXMLReadState;
198 
199 static void
cdxml_simple_end(GsfXMLIn * xin,G_GNUC_UNUSED GsfXMLBlob * blob)200 cdxml_simple_end (GsfXMLIn *xin, G_GNUC_UNUSED GsfXMLBlob *blob)
201 {
202 	CDXMLReadState	*state = (CDXMLReadState *) xin->user_state;
203 	state->cur.top ()->Lock (false);
204 	state->cur.top ()->OnLoaded ();
205 	state->cur.pop ();
206 }
207 
208 static void
cdxml_doc(GsfXMLIn * xin,xmlChar const ** attrs)209 cdxml_doc (GsfXMLIn *xin, xmlChar const **attrs)
210 {
211 	CDXMLReadState	*state = (CDXMLReadState *) xin->user_state;
212 	map<string, unsigned>::iterator it;
213 	double bond_dist_ratio = -1., bond_length = 66.24, x;
214 	state->themedesc << "<?xml version=\"1.0\"?>" << std::endl << "<theme name=\"ChemDraw\"";
215 	state->doc->SetProperty (GCU_PROP_DOC_CREATOR, ""); // Chemdraw does not use it for now.
216 	if (attrs)
217 		while (*attrs) {
218 			std::string key = reinterpret_cast < char const * > (*attrs);
219 			if ((it = KnownProps.find (key)) != KnownProps.end ())
220 				state->doc->SetProperty ((*it).second, reinterpret_cast < char const * > (*(attrs + 1)));
221 			else if (key == "BondLength") {
222 				std::istringstream input (reinterpret_cast < char const * > (*(attrs + 1)));
223 				input >> bond_length;
224 				bond_length *= 8.;
225 				state->themedesc << " bond-length=\"" << bond_length << "\" zoom-factor=\"6\"";
226 				state->doc->SetScale (8.);
227 			} else if (key == "BondSpacing") {
228 				std::istringstream input (reinterpret_cast < char const * > (*(attrs + 1)));
229 				input >> bond_dist_ratio;
230 				bond_dist_ratio /= 100.;
231 			} else if (key == "LineWidth") {
232 				std::istringstream input (reinterpret_cast < char const * > (*(attrs + 1)));
233 				input >> x;
234 				x *= 4. / 3.;
235 				state->themedesc << " bond-width=\"" << x << "\" arrow-width=\"" << x << "\" hash-width=\"" << x << "\"";
236 			} else if (key == "BoldWidth") {
237 				std::istringstream input (reinterpret_cast < char const * > (*(attrs + 1)));
238 				input >> x;
239 				state->themedesc << " stereo-bond-width=\"" << x * 4. / 3. << "\"";
240 			} else if (key == "HashSpacing") {
241 				std::istringstream input (reinterpret_cast < char const * > (*(attrs + 1)));
242 				input >> x;
243 				state->themedesc << " hash-dist=\"" << x * 4. / 3. << "\"";
244 			} else if (key == "ChainAngle")
245 				state->themedesc << " bond-angle=\"" << reinterpret_cast < char const * > (*(attrs + 1)) << "\"";
246 			else if (key == "MarginWidth") {
247 				std::istringstream input (reinterpret_cast < char const * > (*(attrs + 1)));
248 				input >> x;
249 				x *= 4. / 3.;
250 				state->padding = x * 2.;
251 				state->themedesc << " padding=\"" << x << "\" arrow-padding=\"" << x << "\" object-padding=\"" << x << "\" sign-padding=\"" << x << "\"";
252 			} else if (key == "CaptionFont") {
253 				std::istringstream input (reinterpret_cast < char const * > (*(attrs + 1)));
254 				input >> state->captionFont;
255 			} else if (key == "CaptionSize") {
256 				std::istringstream input (reinterpret_cast < char const * > (*(attrs + 1)));
257 				input >> x;
258 				int size = x * PANGO_SCALE;
259 				state->themedesc << " text-font-size=\"" << size << "\"";
260 			} else if (key == "CaptionFace") {
261 				std::istringstream input (reinterpret_cast < char const * > (*(attrs + 1)));
262 				int face;
263 				input >> face;
264 				switch (face & 3) { // we do not support anything else
265 				default:
266 				case 0:
267 					state->themedesc << " text-font-style=\"normal\" text-font-weight=\"normal\"";
268 					break;
269 				case 1:
270 					state->themedesc << " text-font-style=\"normal\" text-font-weight=\"bold\"";
271 					break;
272 				case 2:
273 					state->themedesc << " text-font-style=\"italic\" text-font-weight=\"normal\"";
274 					break;
275 				case 3:
276 					state->themedesc << " text-font-style=\"italic\" text-font-weight=\"bold\"";
277 					break;
278 				}
279 			} else if (key == "LabelFont") {
280 				std::istringstream input (reinterpret_cast < char const * > (*(attrs + 1)));
281 				input >> state->labelFont;
282 			} else if (key == "LabelSize") {
283 				std::istringstream input (reinterpret_cast < char const * > (*(attrs + 1)));
284 				input >> x;
285 				int size = x * PANGO_SCALE;
286 				state->themedesc << " font-size=\"" << size << "\"";
287 			} else if (key == "LabelFace") {
288 				std::istringstream input (reinterpret_cast < char const * > (*(attrs + 1)));
289 				int face;
290 				input >> face;
291 				switch (face & 3) { // we do not support anything else
292 				default:
293 				case 0:
294 					state->themedesc << " font-style=\"normal\" font-weight=\"normal\"";
295 					break;
296 				case 1:
297 					state->themedesc << " font-style=\"normal\" font-weight=\"bold\"";
298 					break;
299 				case 2:
300 					state->themedesc << " font-style=\"italic\" font-weight=\"normal\"";
301 					break;
302 				case 3:
303 					state->themedesc << " font-style=\"italic\" font-weight=\"bold\"";
304 					break;
305 				}
306 			} else if (key == "CaptionJustification") {
307 				std::istringstream input (reinterpret_cast < char const * > (*(attrs + 1)));
308 				input >> state->textAlign;
309 			}
310 			attrs += 2;
311 		}
312 	if (bond_dist_ratio > 0.)
313 		state->themedesc << " bond-dist=\"" << bond_length * bond_dist_ratio / 6. << "\"";
314 	state->cur.push (state->doc);
315 }
316 
317 static void
cdxml_page_start(GsfXMLIn * xin,xmlChar const **)318 cdxml_page_start (GsfXMLIn *xin, xmlChar const **)
319 {
320 	CDXMLReadState	*state = (CDXMLReadState *) xin->user_state;
321 	// we need to set the theme when starting the first page
322 	if (state->theme != NULL)
323 		return;
324 	state->themedesc << "/>";
325 	gcp::Document *cpDoc = dynamic_cast <gcp::Document *> (state->doc);
326 	if (cpDoc != NULL) {
327 		xmlDocPtr xml = xmlParseMemory (state->themedesc.str().c_str(), state->themedesc.str().length());
328 		state->theme = new gcp::Theme (NULL);
329 		state->theme->Load (xml->children);
330 		xmlFreeDoc (xml);
331 		gcp::Theme *LocalTheme = gcp::TheThemeManager.GetTheme (state->theme->GetName ().c_str ());
332 		if (LocalTheme && *LocalTheme == *(state->theme)) {
333 			cpDoc->SetTheme (LocalTheme);
334 			delete state->theme;
335 			state->theme = LocalTheme;  // don't point to an invalid object
336 		} else {
337 			gcp::TheThemeManager.AddFileTheme (state->theme, state->doc->GetTitle ().c_str ());
338 			cpDoc->SetTheme (state->theme);
339 		}
340 		state->CHeight = cpDoc->GetView ()->GetCHeight (); // FIXME, we probably miss a factor, may be 6.
341 	}
342 }
343 
344 static void
cdxml_fragment_start(GsfXMLIn * xin,G_GNUC_UNUSED xmlChar const ** attrs)345 cdxml_fragment_start (GsfXMLIn *xin, G_GNUC_UNUSED xmlChar const **attrs)
346 {
347 	CDXMLReadState	*state = (CDXMLReadState *) xin->user_state;
348 	Object *obj = state->app->CreateObject ("molecule", state->cur.top ());
349 	state->cur.push (obj);
350 	state->doc->ObjectLoaded (obj);
351 	if (attrs)
352 		while (*attrs) {
353 			if (!strcmp (reinterpret_cast < char const * > (*attrs), "id")) {
354 				unsigned id = atoi (reinterpret_cast < char const * > (attrs[1]));
355 				state->loaded_ids[id] = obj->GetId ();
356 			}
357 			attrs += 2;
358 		}
359 }
360 
361 static void
cdxml_fragment_end(GsfXMLIn * xin,G_GNUC_UNUSED GsfXMLBlob * blob)362 cdxml_fragment_end (GsfXMLIn *xin, G_GNUC_UNUSED GsfXMLBlob *blob)
363 {
364 	CDXMLReadState	*state = (CDXMLReadState *) xin->user_state;
365 	static_cast <Molecule*> (state->cur.top ())->UpdateCycles ();
366 	state->cur.top ()->Lock (false);
367 	state->cur.top ()->OnLoaded ();
368 	state->cur.pop ();
369 }
370 
371 static map<string, int>BondTypes;
372 static void
cdxml_bond_start(GsfXMLIn * xin,xmlChar const ** attrs)373 cdxml_bond_start (GsfXMLIn *xin, xmlChar const **attrs)
374 {
375 	CDXMLReadState	*state = (CDXMLReadState *) xin->user_state;
376 	Object *obj = state->app->CreateObject ("bond", state->cur.top ());
377 	obj->SetProperty (GCU_PROP_BOND_ORDER, "1");
378 	map<string, unsigned>::iterator it;
379 	if (attrs)
380 		while (*attrs) {
381 			if ((it = KnownProps.find ((char const *) *attrs++)) != KnownProps.end ()) {
382 				if ((*it).second == GCU_PROP_BOND_TYPE) {
383 					if (BondTypes.empty ()) {
384 						BondTypes["Solid"] = 0;
385 						BondTypes["Dash"] = 1;
386 						BondTypes["Hash"] = 2;
387 						BondTypes["WedgedHashBegin"] = 3;
388 						BondTypes["WedgedHashEnd"] = 4;
389 						BondTypes["Bold"] = 5;
390 						BondTypes["WedgeBegin"] = 6;
391 						BondTypes["WedgeEnd"] = 7;
392 						BondTypes["Wavy"] = 8;
393 						BondTypes["HollowWedgeBegin"] = 9;
394 						BondTypes["HollowWedgeEnd"] = 10;
395 						BondTypes["WavyWedgeBegin"] = 11;
396 						BondTypes["WavyWedgeEnd"] = 12;
397 						BondTypes["Dot"] = 13;
398 						BondTypes["DashDot"] = 14;
399 					}
400 					switch (BondTypes[(char const *) *attrs]) {
401 					case 1:
402 					case 2:
403 					case 3:
404 						obj->SetProperty (GCU_PROP_BOND_TYPE, "hash");
405 						break;
406 					case 4:
407 						obj->SetProperty (GCU_PROP_BOND_TYPE, "hash-invert");
408 						break;
409 					case 5:
410 						obj->SetProperty (GCU_PROP_BOND_TYPE, "large");
411 						break;
412 					case 6:
413 						obj->SetProperty (GCU_PROP_BOND_TYPE, "wedge");
414 						break;
415 					case 7:
416 						obj->SetProperty (GCU_PROP_BOND_TYPE, "wedge-invert");
417 						break;
418 					case 8:
419 						obj->SetProperty (GCU_PROP_BOND_TYPE, "squiggle");
420 						break;
421 					default:
422 						obj->SetProperty (GCU_PROP_BOND_TYPE, "normal");
423 					}
424 				} else if ((*it).second == GCU_PROP_BOND_ORDER) {
425 					unsigned order = atoi ((char const *) *attrs);
426 					switch (order) {
427 					case 2:
428 						obj->SetProperty (GCU_PROP_BOND_ORDER, "2");
429 						break;
430 					case 4:
431 						obj->SetProperty (GCU_PROP_BOND_ORDER, "3");
432 						break;
433 					default:
434 						obj->SetProperty (GCU_PROP_BOND_ORDER, "1");
435 						break;
436 					}
437 				} else if ((*it).second == GCU_PROP_BOND_DOUBLE_POSITION) {
438 					if (!strcmp (reinterpret_cast < char const * > (*attrs), "Center"))
439 					    obj->SetProperty (GCU_PROP_BOND_DOUBLE_POSITION, "center");
440 					else if (!strcmp (reinterpret_cast < char const * > (*attrs), "Right"))
441 					    obj->SetProperty (GCU_PROP_BOND_DOUBLE_POSITION, "right");
442 					else if (!strcmp (reinterpret_cast < char const * > (*attrs), "Left"))
443 					    obj->SetProperty (GCU_PROP_BOND_DOUBLE_POSITION, "left");
444 				} else if (!obj->SetProperty ((*it).second, (char const *) *attrs)) {
445 					CDXMLProps p;
446 					p.obj = obj;
447 					p.property = (*it).second;
448 					p.value = (char const *) *attrs;
449 					state->failed.push_back (p);
450 				}
451 			}
452 			attrs++;
453 		}
454 	state->cur.push (obj);
455 	state->doc->ObjectLoaded (obj);
456 }
457 
458 static void
cdxml_text_start(GsfXMLIn * xin,xmlChar const ** attrs)459 cdxml_text_start (GsfXMLIn *xin, xmlChar const **attrs)
460 {
461 	CDXMLReadState	*state = (CDXMLReadState *) xin->user_state;
462 	gcu::Object *obj = state->app->CreateObject ("text", /*(state->cur.top ()->GetType () == gcu::DocumentType)? */state->cur.top ()/*: NULL*/);
463 	state->cur.push (obj);
464 	state->doc->ObjectLoaded (obj);
465 	char *lowered;
466 	state->line_height = 1;
467 	map<string, unsigned>::iterator it;
468 	if (attrs)
469 		while (*attrs) {
470 			if (!strcmp (reinterpret_cast < char const * > (*attrs), "id")) {
471 				unsigned id = atoi (reinterpret_cast < char const * > (attrs[1]));
472 				state->loaded_ids[id] = obj->GetId ();
473 				attrs += 2;
474 			} else if (!strcmp (reinterpret_cast < char const * > (*attrs), "p")) {
475 				std::istringstream in (reinterpret_cast < char const * > (attrs[1]));
476 				double x, y;
477 				in >> x >> y;
478 				y -= state->CHeight;
479 				std::ostringstream out;
480 				out << x << " " << y;
481 				obj->SetProperty (GCU_PROP_POS2D, out.str ().c_str ());
482 				attrs += 2;
483 			} else if (!strcmp (reinterpret_cast < char const * > (*attrs), "LineHeight") ||
484 			           !strcmp (reinterpret_cast < char const * > (*attrs), "CaptionLineHeight")) {
485 				std::string val (reinterpret_cast < char const * > (attrs[1]));
486 				if (val == "auto")
487 					obj->SetProperty (GCU_PROP_TEXT_VARIABLE_LINE_HEIGHT, "false");
488 				else if (val == "variable")
489 					obj->SetProperty (GCU_PROP_TEXT_VARIABLE_LINE_HEIGHT, "true");
490 				else {
491 					std::istringstream in (val);
492 					in >> state->line_height;
493 				}
494 
495 				attrs += 2;
496 			} else if ((it = KnownProps.find ((char const *) *attrs++)) != KnownProps.end ()) {
497 				lowered = g_ascii_strdown ((char const *) *attrs++, -1);
498 				obj->SetProperty ((*it).second, lowered);
499 				g_free (lowered);
500 			}
501 		}
502 	state->markup = "<text>";
503 }
504 
505 static void
cdxml_text_end(GsfXMLIn * xin,G_GNUC_UNUSED GsfXMLBlob * blob)506 cdxml_text_end (GsfXMLIn *xin, G_GNUC_UNUSED GsfXMLBlob *blob)
507 {
508 	CDXMLReadState	*state = (CDXMLReadState *) xin->user_state;
509 	if (state->cur.top ()->GetParent () == NULL || (gcu::Object::GetTypeName (state->cur.top ()->GetParent ()->GetType ()) == "atom"))
510 		delete state->cur.top ();
511 	else {
512 		state->markup += "</text>";
513 		state->cur.top ()->SetProperty (GCU_PROP_TEXT_MARKUP, state->markup.c_str ());
514 		if (state->line_height > 1) {
515 			state->cur.top ()->SetProperty (GCU_PROP_TEXT_VARIABLE_LINE_HEIGHT, "false");
516 			std::istringstream in (state->cur.top ()->GetProperty (GCU_PROP_TEXT_MAX_LINE_HEIGHT));
517 			double lh;
518 			in >> lh;
519 			std::ostringstream out;
520 			out << state->line_height - lh;
521 			state->cur.top ()->SetProperty (GCU_PROP_TEXT_INTERLINE, out.str ().c_str ());
522 		}
523 	}
524 	state->markup.clear ();
525 	state->cur.pop ();
526 }
527 
528 static void
cdxml_string_start(GsfXMLIn * xin,xmlChar const ** attrs)529 cdxml_string_start (GsfXMLIn *xin, xmlChar const **attrs)
530 {
531 	CDXMLReadState	*state = (CDXMLReadState *) xin->user_state;
532 	state->attributes = 0;
533 	if (attrs)
534 		while (*attrs) {
535 			if (!strcmp ((char const *) *attrs, "font")) {
536 				attrs++;
537 				state->font = atoi ((char const *) *attrs);
538 				state->markup += "<font name=\"";
539 				state->markup += state->fonts[state->font].name;
540 				state->markup += ",";
541 			} else if (!strcmp ((char const *) *attrs, "face"))  {
542 				attrs++;
543 				state->attributes |= atoi ((char const *) *attrs);
544 			} else if (!strcmp ((char const *) *attrs, "size"))  {
545 				attrs++;
546 				state->size = (char const *) *attrs;
547 			} else if (!strcmp ((char const *) *attrs, "color"))  {
548 				attrs++;
549 				state->attributes |= 0x100;
550 				state->color = atoi ((char const *) *attrs);
551 			} else
552 				attrs ++;
553 			attrs++;
554 		}
555 	state->markup += state->size + "\">";
556 	if (state->attributes & 0x100)
557 		state->markup += string ("<fore ") + state->colors[state->color] + ">";
558 	if (state->attributes & 1)
559 		state->markup += "<b>";
560 	if (state->attributes & 2)
561 		state->markup += "<i>";
562 	if (state->attributes & 4)
563 		state->markup += "<u>";
564 	if ((state->attributes & 0x60) != 0x60) {
565 		if (state->attributes & 0x20)
566 			state->markup += "<sub>";
567 		else if (state->attributes & 0x40)
568 			state->markup += "<sup>";
569 	}
570 
571 	// TODO: parse attributes
572 }
573 
574 static void
cdxml_string_end(GsfXMLIn * xin,G_GNUC_UNUSED GsfXMLBlob * blob)575 cdxml_string_end (GsfXMLIn *xin, G_GNUC_UNUSED GsfXMLBlob *blob)
576 {
577 	CDXMLReadState	*state = (CDXMLReadState *) xin->user_state;
578 	bool opened = true;
579 	if ((state->attributes & 0x60) == 0x60) {
580 		// for now put all numbers as subscripts
581 		// FIXME: fix this kludgy code
582 		int cur = 0, size = strlen (xin->content->str);
583 		char new_size[G_ASCII_DTOSTR_BUF_SIZE], height[G_ASCII_DTOSTR_BUF_SIZE];
584 		g_ascii_dtostr (new_size, G_ASCII_DTOSTR_BUF_SIZE, g_ascii_strtod (state->size.c_str (), NULL) / 1.5);
585 		g_ascii_dtostr (height, G_ASCII_DTOSTR_BUF_SIZE, g_ascii_strtod (state->size.c_str (), NULL) / 3.);
586 		while (cur < size) {
587 			while (cur < size && (xin->content->str[cur] < '0' || xin->content->str[cur] > '9'))
588 				state->markup += xin->content->str[cur++];
589 			if (cur < size) {
590 				if (state->attributes & 4)
591 					state->markup += "</u>";
592 				if (state->attributes & 2)
593 					state->markup += "</i>";
594 				if (state->attributes & 1)
595 					state->markup += "</b>";
596 				if (state->attributes & 0x100)
597 					state->markup += "</fore>";
598 				state->markup += string ("</font><font name=\"") + state->fonts[state->font].name + " " + new_size + "\">";
599 				if (state->attributes & 0x100)
600 					state->markup += string ("<fore ") + state->colors[state->color] + ">";
601 				state->markup += string ("<sub height=\"") + height + "\">";
602 				while (xin->content->str[cur] >= '0' && xin->content->str[cur] <= '9')
603 					state->markup += xin->content->str[cur++];
604 				state->markup += "</sub>";
605 				if (state->attributes & 0x100)
606 					state->markup += "</fore>";
607 				state->markup += string ("</font>");
608 				if (cur < size) {
609 					state->markup += string ("<font name=\"") + state->fonts[state->font].name + " " + state->size + "\">";
610 					if (state->attributes & 0x100)
611 						state->markup += string ("<fore ") + state->colors[state->color] + ">";
612 					if (state->attributes & 1)
613 						state->markup += "<b>";
614 					if (state->attributes & 2)
615 						state->markup += "<i>";
616 					if (state->attributes & 4)
617 						state->markup += "<u>";
618 				} else
619 					opened = false;
620 			}
621 		}
622 	} else {
623 		state->markup += xin->content->str;
624 		if (state->attributes & 0x20)
625 			state->markup += "</sub>";
626 		else if (state->attributes & 0x40)
627 			state->markup += "</sup>";
628 	}
629 	if (opened) {
630 		if (state->attributes & 4)
631 			state->markup += "</u>";
632 		if (state->attributes & 2)
633 			state->markup += "</i>";
634 		if (state->attributes & 1)
635 			state->markup += "</b>";
636 		if (state->attributes & 0x100)
637 			state->markup += "</fore>";
638 		state->markup += "</font>";
639 	}
640 	state->attributes = 0;
641 }
642 
643 static void
fragment_done(G_GNUC_UNUSED GsfXMLIn * xin,CDXMLReadState * state)644 fragment_done (G_GNUC_UNUSED GsfXMLIn *xin, CDXMLReadState *state)
645 {
646 	Object *atom = state->cur.top (), *child;
647 	state->cur.pop ();
648 	map <string, Object *>::iterator i;
649 	Molecule *mol = NULL, *mol1 = NULL;
650 	string buf;
651 	//TODO: retreive text and molecule and compare
652 	while ((child = atom->GetFirstChild (i))) {
653 		child->SetParent (NULL);
654 		if (child->GetType () == MoleculeType)
655 			mol = dynamic_cast <Molecule *> (child);
656 		else {
657 			buf = child->GetProperty (GCU_PROP_TEXT_TEXT);
658 			delete child;
659 		}
660 	}
661 	if (mol) {
662 		if (buf.length () > 0) {
663 			try {
664 				Formula form (buf, GCU_FORMULA_PARSE_RESIDUE);
665 				mol1 = Molecule::MoleculeFromFormula (state->doc, form);
666 				bool have_pseudo = false;
667 				Object *obj = mol->GetFirstChild (i);
668 				gcu::Atom *a = NULL;
669 				while (obj) {
670 					a = dynamic_cast <gcu::Atom *> (obj);
671 					if (a && ! a->GetZ ()) {
672 						have_pseudo = true;
673 						break;
674 					}
675 					obj = mol->GetNextChild (i);
676 				}
677 				if (!mol1 || !(*mol == *mol1)) {
678 					if (have_pseudo) {
679 						// try adding a new residue
680 						// first examine the first atom
681 						map <gcu::Atom*, gcu::Bond*>::iterator i;
682 						gcu::Bond *b = a->GetFirstBond (i);
683 						int residue_offset = 0;
684 						if (!b)
685 							goto fragment_error;
686 						gcu::Atom *a2 = b->GetAtom (a);
687 						if (!a2)
688 							goto fragment_error;
689 						list<FormulaElt *> const &elts = form.GetElements ();
690 						list<FormulaElt *>::const_iterator j = elts.begin ();
691 						FormulaAtom *fatom = dynamic_cast <FormulaAtom *> (*j);
692 						int valence;
693 						if (!fatom || fatom->elt != a2->GetZ ())
694 							goto fragment_add;
695 						valence = Element::GetElement (fatom->elt)->GetDefaultValence ();
696 						switch (valence) {
697 						case 2: {
698 							/* remove the first atom and replace it by a pseudo-atom, then add the residue
699 							this helps with things begining with an oxygen or a sulfur, but might be
700 							not enough n other cases */
701 							double x, y;
702 							a2->GetCoords (&x, &y);
703 							a->SetCoords (x, y);
704 							a->RemoveBond (b);
705 							a2->RemoveBond (b);
706 							mol->Remove (b);
707 							delete b;
708 							if (a2->GetBondsNumber () > 1)
709 								goto fragment_error;
710 							b = a2->GetFirstBond (i);
711 							if (b->GetOrder () != 1)
712 								goto fragment_error;
713 							b->ReplaceAtom (a2, a);
714 							a->AddBond (b);
715 							mol->Remove (a2);
716 							delete a2;
717 							// now remove the atom from the new residue symbol
718 							residue_offset += fatom->end;
719 							break;
720 						}
721 						case 3:
722 							// we do not support that at the moment
723 							goto fragment_error;
724 							break;
725 						default:
726 							// we do not support that at the moment
727 							goto fragment_error;
728 						}
729 fragment_add:
730 						// Try create a new document, using the symbol as name
731 						// reparent the molecule to avoid a crash
732 						state->doc->AddChild (mol);
733 						state->doc->CreateResidue (buf.c_str () + residue_offset, buf.c_str () + residue_offset, mol);
734 						mol = NULL;
735 						goto fragment_success;
736 					}
737 fragment_error:
738 					g_warning (_("failed for %s\n"),buf.c_str ());
739 				}
740 			}
741 			catch (parse_error &error) {
742 				int start, length;
743 				puts (error.what (start, length));
744 			}
745 fragment_success:
746 			string pos = atom->GetProperty (GCU_PROP_POS2D);
747 			string id = atom->GetId ();
748 			mol = reinterpret_cast <Molecule *> (state->cur.top ());
749 			mol->Remove (atom);
750 			delete atom;
751 			atom = state->app->CreateObject ("fragment", mol);
752 			atom->SetProperty (GCU_PROP_TEXT_TEXT, buf.c_str ());
753 			atom->SetProperty (GCU_PROP_FRAGMENT_ATOM_ID, id.c_str ());
754 			atom->SetProperty (GCU_PROP_FRAGMENT_ATOM_START, "0");
755 			atom->SetProperty (GCU_PROP_POS2D, pos.c_str ());
756 			if (mol1) {
757 				mol1->SetParent (NULL);
758 				delete mol1;
759 			}
760 			mol = NULL;
761 		}
762 		if (mol)
763 			delete mol;
764 	} else goto fragment_success;
765 }
766 
767 static void
cdxml_node_start(GsfXMLIn * xin,xmlChar const ** attrs)768 cdxml_node_start (GsfXMLIn *xin, xmlChar const **attrs)
769 {
770 	static GsfXMLInNode const atom_dtd[] = {
771 	GSF_XML_IN_NODE (ATOM, ATOM, -1, "n", GSF_XML_CONTENT, NULL, NULL),
772 			GSF_XML_IN_NODE (ATOM, T, -1, "t", GSF_XML_CONTENT, cdxml_text_start, cdxml_text_end),
773 				GSF_XML_IN_NODE (T, S, -1, "s", GSF_XML_CONTENT, cdxml_string_start, cdxml_string_end),
774 			GSF_XML_IN_NODE (ATOM, FRAGMENT, -1, "fragment", GSF_XML_CONTENT, cdxml_fragment_start, cdxml_fragment_end),
775 				GSF_XML_IN_NODE (FRAGMENT, NODE, -1, "n", GSF_XML_CONTENT, cdxml_node_start, cdxml_simple_end),
776 				GSF_XML_IN_NODE (FRAGMENT, BOND, -1, "b", GSF_XML_CONTENT, cdxml_bond_start, cdxml_simple_end),
777 				GSF_XML_IN_NODE (FRAGMENT, T1, -1, "t", GSF_XML_CONTENT, NULL, NULL),
778 					GSF_XML_IN_NODE (T1, S1, -1, "s", GSF_XML_CONTENT, cdxml_string_start, cdxml_string_end),
779 	GSF_XML_IN_NODE_END
780 	};
781 	CDXMLReadState	*state = (CDXMLReadState *) xin->user_state;
782 	Object *obj = state->app->CreateObject ("atom", state->cur.top ());
783 	obj->SetProperty (GCU_PROP_ATOM_Z, "6");
784 	state->doc->ObjectLoaded (obj);
785 	map<string, unsigned>::iterator it;
786 	bool fragment = false;
787 	if (attrs)
788 		while (*attrs) {
789 			if ((it = KnownProps.find ((char const *) *attrs)) != KnownProps.end ()) {
790 				attrs++;
791 				obj->SetProperty ((*it).second, (char const *) *attrs);
792 			} else if (!strcmp ((char const *) *attrs++, "NodeType")) {
793 				if (!strcmp ((char const *) *attrs, "Fragment") ||
794 					!strcmp ((char const *) *attrs, "Nickname") ||
795 					!strcmp ((char const *) *attrs, "Unspecified") ||
796 					!strcmp ((char const *) *attrs, "GenericNickname"))
797 					fragment = true;
798 				else if (!strcmp ((char const *) *attrs, "ExternalConnectionPoint")) {
799 					// convert the atom to a pseudo atom.
800 					string pos = obj->GetProperty (GCU_PROP_POS2D);
801 					string id = obj->GetProperty (GCU_PROP_ID);
802 					Molecule *mol = dynamic_cast <Molecule *> (state->cur.top ());
803 					if (mol)
804 						mol->Remove (obj);
805 					delete obj;
806 					obj = state->app->CreateObject ("pseudo-atom", state->cur.top ());
807 					if (id.length ())
808 						obj->SetProperty (GCU_PROP_ID, id.c_str ());
809 					obj->SetProperty (GCU_PROP_POS2D, pos.c_str ());
810 				}
811 				attrs++;
812 			}
813 			attrs++;
814 		}
815 	state->cur.push (obj);
816 	if (fragment) {
817 		static GsfXMLInDoc *doc = NULL;
818 		if (NULL == doc)
819 			doc = gsf_xml_in_doc_new (atom_dtd, NULL);
820 		state->cur.push (obj); // push it twice in that case
821 		state->doc->ObjectLoaded (obj);
822 		gsf_xml_in_push_state (xin, doc, state, (GsfXMLInExtDtor) fragment_done, attrs);
823 	}
824 }
825 
826 static void
cdxml_font_start(GsfXMLIn * xin,xmlChar const ** attrs)827 cdxml_font_start (GsfXMLIn *xin, xmlChar const **attrs)
828 {
829 	CDXMLReadState	*state = (CDXMLReadState *) xin->user_state;
830 	CDXMLFont font;
831 	font.index = 0;
832 	if (attrs)
833 		while (*attrs) {
834 			if (!strcmp ((char const *) *attrs, "id"))
835 				font.index = atoi ((char const *) *(attrs + 1));
836 			else if (!strcmp ((char const *) *attrs, "charset"))
837 				font.encoding = (char const *) *(attrs + 1);
838 			else if (!strcmp ((char const *) *attrs, "name"))
839 				font.name = (char const *) *(attrs + 1);
840 			attrs += 2;
841 		}
842 	if (state->labelFont == font.index)
843 		state->themedesc << " font-family=\"" << font.name << "\"";
844 	if (state->captionFont == font.index)
845 		state->themedesc << " text-font-family=\"" << font.name << "\"";
846 	state->fonts[font.index] = font;
847 }
848 
849 static void
cdxml_color(GsfXMLIn * xin,xmlChar const ** attrs)850 cdxml_color (GsfXMLIn *xin, xmlChar const **attrs)
851 {
852 	CDXMLReadState	*state = (CDXMLReadState *) xin->user_state;
853 	string red, green, blue;
854 	if (attrs)
855 		while (*attrs) {
856 			if (!strcmp ((char const *) *attrs, "r"))
857 				red = (char const *) attrs[1];
858 			else if (!strcmp ((char const *) *attrs, "g"))
859 				green = (char const *) attrs[1];
860 			else if (!strcmp ((char const *) *attrs, "b"))
861 				blue = (char const *) attrs[1];
862 			attrs += 2;
863 		}
864 	state->colors.push_back (string ("red=\"") + red + "\" green=\"" + green + "\" blue=\"" + blue + "\"");
865 }
866 
867 static void
cdxml_group_start(GsfXMLIn * xin,G_GNUC_UNUSED xmlChar const ** attrs)868 cdxml_group_start (GsfXMLIn *xin, G_GNUC_UNUSED xmlChar const **attrs)
869 {
870 	CDXMLReadState	*state = (CDXMLReadState *) xin->user_state;
871 	Object *obj = state->app->CreateObject ("group", state->cur.top ());
872 	obj->Lock ();
873 	state->cur.push (obj);
874 	state->doc->ObjectLoaded (obj);
875 }
876 
877 static void
cdxml_graphic_start(GsfXMLIn * xin,xmlChar const ** attrs)878 cdxml_graphic_start (GsfXMLIn *xin, xmlChar const **attrs)
879 {
880 	CDXMLReadState	*state = (CDXMLReadState *) xin->user_state;
881 	guint32 Id = 0;
882 	guint16 type = 0xffff, arrow_type = 0xffff;
883 	double x0, y0, x1, y1;
884 	if (attrs)
885 		while (*attrs) {
886 			if (!strcmp ((char const *) *attrs, "id"))
887 				Id = atoi ((char const *) attrs[1]);
888 			else if (!strcmp ((char const *) *attrs, "BoundingBox")) {
889 				istringstream str (reinterpret_cast <char const *> (attrs[1]));
890 				str >> x1 >> y1 >> x0 >> y0;
891 			} else if (!strcmp ((char const *) *attrs, "GraphicType")) {
892 				if (!strcmp ((char const *) attrs[1], "Line"))
893 					type = 1;
894 			} else if (!strcmp ((char const *) *attrs, "ArrowType")) {
895 				if (!strcmp ((char const *) attrs[1], "FullHead") || !strcmp ((char const *) attrs[1], "HalfHead"))
896 					arrow_type = 2;
897 				else if (!strcmp ((char const *) attrs[1], "Resonance"))
898 					arrow_type = 4;
899 				else if (!strcmp ((char const *) attrs[1], "Equilibrium"))
900 					arrow_type = 8;
901 				else if (!strcmp ((char const *) attrs[1], "Hollow"))
902 					arrow_type = 16;
903 				else if (!strcmp ((char const *) attrs[1], "RetroSynthetic"))
904 					arrow_type = 32;
905 			}
906 			attrs+=2;
907 		}
908 	if (type == 1) {
909 		Object *obj = NULL;
910 		ostringstream str;
911 		switch (arrow_type) {
912 		case 1:
913 		case 2:
914 			obj = state->app->CreateObject ("reaction-arrow", state->cur.top ());
915 			str << "ra" << Id;
916 			break;
917 		case 4:
918 			obj = state->app->CreateObject ("mesomery-arrow", state->cur.top ());
919 			str << "ma" << Id;
920 			break;
921 		case 8:
922 			obj = state->app->CreateObject ("reaction-arrow", state->cur.top ());
923 			str << "ra" << Id;
924 			obj->SetProperty (GCU_PROP_REACTION_ARROW_TYPE, "double");
925 			break;
926 		case 32:
927 			obj = state->app->CreateObject ("retrosynthesis-arrow", state->cur.top ());
928 			str << "rsa" << Id;
929 			break;
930 		default:
931 			break;
932 		}
933 		if (obj) {
934 			obj->SetId (str.str ().c_str ());
935 			state->loaded_ids[Id] = str.str ();
936 			ostringstream str_;
937 			str_ << x0 << " " << y0 << " " << x1 << " " << y1;
938 			obj->SetProperty (GCU_PROP_ARROW_COORDS, str_.str ().c_str ());
939 			state->doc->ObjectLoaded (obj);
940 		}
941 	}
942 }
943 
944 static void
cdxml_step_start(GsfXMLIn * xin,G_GNUC_UNUSED xmlChar const ** attrs)945 cdxml_step_start (GsfXMLIn *xin, G_GNUC_UNUSED xmlChar const **attrs)
946 {
947 	CDXMLReadState	*state = (CDXMLReadState *) xin->user_state;
948 	StepData data;
949 	if (attrs)
950 		while (*attrs) {
951 			std::string key = reinterpret_cast < char const * > (*attrs);
952 			std::istringstream values (reinterpret_cast < char const * > (attrs[1]));
953 			attrs +=2;
954 			std::list < unsigned > *target;
955 			if (key == "ReactionStepReactants")
956 				target = &data.Reagents;
957 			else if (key == "ReactionStepProducts")
958 				target = &data.Products;
959 			else if (key == "ReactionStepArrows")
960 				target = &data.Arrows;
961 			else if (key == "ReactionStepObjectsAboveArrow")
962 				target = &data.ObjectsAbove;
963 			else if (key == "ReactionStepObjectsBelowArrow")
964 				target = &data.ObjectsBelow;
965 			else
966 				continue;
967 			while (!values.eof ()) {
968 				unsigned id;
969 				values >> id;
970 				target->push_back (id);
971 			}
972 		}
973 	state->scheme.Steps.push_back (data);
974 }
975 
976 static void
cdxml_scheme_end(GsfXMLIn * xin,G_GNUC_UNUSED GsfXMLBlob * blob)977 cdxml_scheme_end (GsfXMLIn *xin, G_GNUC_UNUSED GsfXMLBlob *blob)
978 {
979 	CDXMLReadState	*state = (CDXMLReadState *) xin->user_state;
980 	state->schemes.push_back (state->scheme);
981 	state->scheme.Steps.clear ();
982 }
983 
build_scheme(CDXMLReadState & state,SchemeData & scheme)984 static void build_scheme (CDXMLReadState &state, SchemeData &scheme)
985 {
986 	gcu::Document *doc = state.doc;
987 	std::list < StepData >::iterator i, iend = scheme.Steps.end ();
988 	std::list < unsigned >::iterator j, jend;
989 	int IsReaction = 0, IsMesomery = 0, IsRetrosynthesis = 0;
990 	bool HasMesomeryArrows = false;
991 	gcu::Object *parent, *arrow, *obj, *step, *reactant;
992 	for (i = scheme.Steps.begin (); i != iend; i++) {
993 		if ((*i).Arrows.size () != 1)
994 			return; // unsupported feature, don't load the scheme
995 		obj = doc->GetChild ((state.loaded_ids[*((*i).Arrows.begin())]).c_str ());
996 		if (obj == NULL)
997 			return;
998 		std::string klass = gcu::Object::GetTypeName (obj->GetType ());
999 		if (klass == "retrosynthesis-arrow") {
1000 			if (IsRetrosynthesis == -1)
1001 				return;
1002 			IsRetrosynthesis = 1;
1003 			IsReaction = IsMesomery = -1;
1004 		} else if (klass ==  "mesomery-arrow") {
1005 			if (IsMesomery == -1)
1006 				return;
1007 			IsRetrosynthesis = -1;
1008 			if (IsMesomery == 0 && IsReaction == 0)
1009 				IsMesomery = 1;
1010 			HasMesomeryArrows = true;
1011 		} else if (klass ==  "reaction-arrow") {
1012 			if (IsReaction == -1 || IsMesomery == -1)
1013 				return;
1014 			IsReaction = 1;
1015 			IsRetrosynthesis = -1;
1016 			IsMesomery = 0;
1017 		} else
1018 			return;
1019 	}
1020 	if (IsRetrosynthesis == 1) {
1021 		gcu::Object *retrosynthesis = doc->CreateObject ("retrosynthesis", doc);
1022 		std::set < std::string > targets;
1023 		std::set < std::string >::iterator target;
1024 		ostringstream str;
1025 		str << "rsy" << scheme.Id;
1026 		retrosynthesis->SetId (str.str ().c_str ());
1027 		state.loaded_ids[scheme.Id] = retrosynthesis->GetId ();
1028 		// now, add the objects to the retrosynthesis
1029 		for (i = scheme.Steps.begin (); i != iend; i++) {
1030 			if ((*i).Reagents.size () != 1 || (*i).Products.size () != 1) {
1031 				delete retrosynthesis;
1032 				return;
1033 			}
1034 			// first the arrow
1035 			arrow = doc->GetChild ((state.loaded_ids[*((*i).Arrows.begin())]).c_str ());
1036 			obj = doc->GetDescendant (state.loaded_ids[*(*i).Reagents.begin ()].c_str ());
1037 			parent = obj->GetParent ();
1038 			if (parent == doc)
1039 				parent = doc->CreateObject ("retrosynthesis-step", retrosynthesis);
1040 			else if (parent->GetParent () != retrosynthesis) {
1041 				delete retrosynthesis;
1042 				return;
1043 			}
1044 			parent->SetProperty (GCU_PROP_MOLECULE, obj->GetId ());
1045 			arrow->SetProperty (GCU_PROP_ARROW_START_ID, parent->GetId ());
1046 			targets.insert (parent->GetId ());
1047 			obj = doc->GetDescendant (state.loaded_ids[*(*i).Products.begin ()].c_str ());
1048 			parent = obj->GetParent ();
1049 			if (parent == doc)
1050 				parent = doc->CreateObject ("retrosynthesis-step", retrosynthesis);
1051 			else if (parent->GetParent () != retrosynthesis) {
1052 				delete retrosynthesis;
1053 				return;
1054 			}
1055 			parent->AddChild (obj);
1056 			arrow->SetProperty (GCU_PROP_ARROW_END_ID, parent->GetId ());
1057 			target = targets.find (parent->GetId ());
1058 			if (target != targets.end ())
1059 				targets.erase (target);
1060 			retrosynthesis->AddChild (arrow);
1061 		}
1062 		if (targets.size () != 1) {
1063 			delete retrosynthesis;
1064 			return;
1065 		}
1066 		// using GCU_PROP_MOLECULE even if not ideal (the target is a step, not the molecule inside)
1067 		retrosynthesis->SetProperty (GCU_PROP_MOLECULE, (*targets.begin()).c_str ());
1068 		// Ignore objects over and under the arrows for now
1069 	} else if (IsMesomery == 1) {
1070 		gcu::Object *mesomery = doc->CreateObject ("mesomery", doc);
1071 		ostringstream str;
1072 		str << "msy" << scheme.Id;
1073 		mesomery->SetId (str.str ().c_str ());
1074 		state.loaded_ids[scheme.Id] = mesomery->GetId ();
1075 		// now, add the objects to the mesomery
1076 		for (i = scheme.Steps.begin (); i != iend; i++) {
1077 			if ((*i).Reagents.size () != 1 || (*i).Products.size () != 1) {
1078 				delete mesomery;
1079 				return;
1080 			}
1081 			// first the arrow
1082 			arrow = doc->GetChild ((state.loaded_ids[*((*i).Arrows.begin())]).c_str ());
1083 			obj = doc->GetDescendant (state.loaded_ids[*(*i).Reagents.begin ()].c_str ());
1084 			parent = obj->GetParent ();
1085 			if (parent == doc)
1086 				parent = doc->CreateObject ("mesomer", mesomery);
1087 			else if (parent->GetParent () != mesomery) {
1088 				delete mesomery;
1089 				return;
1090 			}
1091 			parent->SetProperty (GCU_PROP_MESOMER, obj->GetId ());
1092 			arrow->SetProperty (GCU_PROP_ARROW_START_ID, parent->GetId ());
1093 			obj = doc->GetDescendant (state.loaded_ids[*(*i).Products.begin ()].c_str ());
1094 			parent = obj->GetParent ();
1095 			if (parent == doc)
1096 				parent = doc->CreateObject ("mesomer", mesomery);
1097 			else if (parent->GetParent () != mesomery) {
1098 				delete mesomery;
1099 				return;
1100 			}
1101 			parent->AddChild (obj);
1102 			arrow->SetProperty (GCU_PROP_ARROW_END_ID, parent->GetId ());
1103 			mesomery->AddChild (arrow);
1104 		}
1105 		// Ignore objects over and under the arrows for now
1106 	} else if (IsReaction ==1) {
1107 		if (HasMesomeryArrows) {
1108 			// build mesomeries inside reactions,
1109 			// FIXME: nots supported for now
1110 			return;
1111 		}
1112 		gcu::Object *reaction = doc->CreateObject ("reaction", doc);
1113 		ostringstream str;
1114 		str << "r" << scheme.Id;
1115 		reaction->SetId (str.str ().c_str ());
1116 		state.loaded_ids[scheme.Id] = reaction->GetId ();
1117 		// now, add the objects to the reaction
1118 		for (i = scheme.Steps.begin (); i != iend; i++) {
1119 			// first the arrow
1120 			arrow = doc->GetChild ((state.loaded_ids[*((*i).Arrows.begin())]).c_str ());
1121 			reaction->AddChild (arrow);
1122 			// then reagents
1123 			jend = (*i).Reagents.end ();
1124 			parent = NULL;
1125 			gcu::Object *rs = NULL; // make g++ happy
1126 			for (j = (*i).Reagents.begin (); j != jend; j++) {
1127 				obj = doc->GetDescendant (state.loaded_ids[*j].c_str ());
1128 				if (obj == NULL) {
1129 					delete reaction;
1130 					return;
1131 				}
1132 				parent = obj->GetParent ();
1133 				if (rs == NULL) {
1134 					if (parent == doc) {
1135 						rs = reaction->CreateObject ("reaction-step", reaction);
1136 						arrow->SetProperty (GCU_PROP_ARROW_START_ID, rs->GetId ());
1137 						reactant = rs->CreateObject ("reactant", rs);
1138 						reactant->SetProperty (GCU_PROP_MOLECULE, obj->GetId ());
1139 					} else {
1140 						rs = parent->GetParent ();
1141 						if (rs->GetParent () != reaction) {
1142 							delete reaction;
1143 							return;
1144 						}
1145 					}
1146 				} else {
1147 					if (parent == doc) {
1148 						reactant = rs->CreateObject ("reactant", rs);
1149 						reactant->SetProperty (GCU_PROP_MOLECULE, obj->GetId ());
1150 					} else if (rs != parent->GetParent ()) {
1151 						delete reaction;
1152 						return;
1153 					}
1154 				}
1155 				// search for potential stoichiometry coefficients
1156 				arrow->SetProperty (GCU_PROP_ARROW_START_ID, rs->GetId ());
1157 				rs->OnLoaded ();
1158 			}
1159 			// same treatment for products
1160 			jend = (*i).Products.end ();
1161 			rs = NULL;
1162 			for (j = (*i).Products.begin (); j != jend; j++) {
1163 				obj = doc->GetDescendant (state.loaded_ids[*j].c_str ());
1164 				if (obj == NULL) {
1165 					delete reaction;
1166 					return;
1167 				}
1168 				parent = obj->GetParent ();
1169 				if (rs == NULL) {
1170 					if (parent == doc) {
1171 						rs = reaction->CreateObject ("reaction-step", reaction);
1172 						arrow->SetProperty (GCU_PROP_ARROW_END_ID, rs->GetId ());
1173 						reactant = rs->CreateObject ("reactant", rs);
1174 						reactant->SetProperty (GCU_PROP_MOLECULE, obj->GetId ());
1175 					} else {
1176 						rs = parent->GetParent ();
1177 						if (rs->GetParent () != reaction) {
1178 							delete reaction;
1179 							return;
1180 						}
1181 					}
1182 				} else {
1183 					if (parent == doc) {
1184 						reactant = rs->CreateObject ("reactant", rs);
1185 						reactant->SetProperty (GCU_PROP_MOLECULE, obj->GetId ());
1186 					} else if (rs != parent->GetParent ()) {
1187 						delete reaction;
1188 						return;
1189 					}
1190 				}
1191 				// search for potential stoichiometry coefficients
1192 				arrow->SetProperty (GCU_PROP_ARROW_END_ID, rs->GetId ());
1193 				rs->OnLoaded ();
1194 			}
1195 			// last, the objects attached above and below the arrow
1196 			if (!(*i).ObjectsAbove.empty () || !(*i).ObjectsBelow.empty ()) {
1197 				jend = (*i).ObjectsAbove.end ();
1198 				for (j = (*i).ObjectsAbove.begin (); j != jend; j++) {
1199 					obj = doc->GetDescendant (state.loaded_ids[*j].c_str ());
1200 					if (obj == NULL) // we should emit at least a warning
1201 						continue;
1202 					parent = arrow->CreateObject ("reaction-prop", arrow);
1203 					parent->SetProperty (GCU_PROP_ARROW_OBJECT, obj->GetId ());
1204 				}
1205 				jend = (*i).ObjectsBelow.end ();
1206 				for (j = (*i).ObjectsBelow.begin (); j != jend; j++) {
1207 					obj = doc->GetDescendant (state.loaded_ids[*j].c_str ());
1208 					if (obj == NULL) // we should emit at least a warning
1209 						continue;
1210 					parent = arrow->CreateObject ("reaction-prop", arrow);
1211 					parent->SetProperty (GCU_PROP_ARROW_OBJECT, obj->GetId ());
1212 				}
1213 			}
1214 		}
1215 		// now search for stoichiometry coefficients if any
1216 		gcp::WidgetData *data = static_cast <gcp::Document * > (doc)->GetView ()->GetData ();
1217 		gccv::Rect rect;
1218 		double x0, y0, x1;
1219 		std::map < std::string, Object * >::iterator k, l, r;
1220 		std::pair <gcu::Object *, gcu::Object * > couple;
1221 		std::list < std::pair <gcu::Object *, gcu::Object * > > couples;
1222 		obj = doc->GetFirstChild (k);
1223 		while (obj) {
1224 			// assuming that only text object can be stoichiometric coefs
1225 			if (obj->GetType () == gcu::TextType) {
1226 				data->GetObjectBounds (obj, rect);
1227 				x0 = rect.x0;
1228 				y0 = (rect.y0 + rect.y1) / 2.;
1229 				x1 = rect.x1 + state.padding;
1230 				for (step = reaction->GetFirstChild (l); step; step = reaction->GetNextChild (l)) {
1231 					if (gcu::Object::GetTypeName (step->GetType ()) != "reaction-step")
1232 						continue;
1233 					data->GetObjectBounds (step, rect);
1234 					if (x0 > rect.x1 || x1 < rect.x0 || y0 > rect.y1 || y0 < rect.y0)
1235 						continue;
1236 					for (reactant = step->GetFirstChild (r); reactant; reactant = step->GetNextChild (r)) {
1237 						if (reactant->GetType () != gcu::ReactantType)
1238 							continue;
1239 						data->GetObjectBounds (reactant, rect);
1240 						if (x0 > rect.x0 || x1 < rect.x0 || y0 > rect.y1 || y0 < rect.y0)
1241 							continue;
1242 						// if we get there, we got it
1243 						// we must not set it now to avoid an invalid iterator at this point, so store in couples.
1244 						couple.first = reactant;
1245 						couple.second = obj;
1246 						couples.push_back (couple);
1247 						goto next_text;
1248 					}
1249 				}
1250 			}
1251 next_text:
1252 			obj = doc->GetNextChild (k);
1253 		}
1254 		std::list < std::pair <gcu::Object *, gcu::Object * > >::iterator c, cend = couples.end ();
1255 		for (c = couples.begin (); c != cend; c++) {
1256 			(*c).first->SetProperty (GCU_PROP_STOICHIOMETRY, (*c).second->GetId ());
1257 		}
1258 	}
1259 }
1260 
1261 ////////////////////////////////////////////////////////////////////////////////
1262 // Reading code
1263 static GsfXMLInNode const cdxml_dtd[] = {
1264 GSF_XML_IN_NODE (CDXML, CDXML, -1, "CDXML", GSF_XML_CONTENT, &cdxml_doc, NULL),
1265 	GSF_XML_IN_NODE (CDXML, COLORTABLE, -1, "colortable", GSF_XML_CONTENT, NULL, NULL),
1266 		GSF_XML_IN_NODE (COLORTABLE, COLOR, -1, "color", GSF_XML_CONTENT, &cdxml_color, NULL),
1267 	GSF_XML_IN_NODE (CDXML, FONTTABLE, -1, "fonttable", GSF_XML_CONTENT, NULL, NULL),
1268 		GSF_XML_IN_NODE (FONTTABLE, FONT, -1, "font", GSF_XML_CONTENT, cdxml_font_start, NULL),
1269 	GSF_XML_IN_NODE (CDXML, PAGE, -1, "page", GSF_XML_CONTENT, cdxml_page_start, NULL),
1270 		GSF_XML_IN_NODE (PAGE, T, -1, "t", GSF_XML_NO_CONTENT, cdxml_text_start, cdxml_text_end),
1271 			GSF_XML_IN_NODE (T, S, -1, "s", GSF_XML_CONTENT, cdxml_string_start, cdxml_string_end),
1272 		GSF_XML_IN_NODE (PAGE, FRAGMENT, -1, "fragment", GSF_XML_CONTENT, &cdxml_fragment_start, &cdxml_fragment_end),
1273 			GSF_XML_IN_NODE (FRAGMENT, NODE, -1, "n", GSF_XML_CONTENT, cdxml_node_start, cdxml_simple_end),
1274 				GSF_XML_IN_NODE (NODE, T, -1, "t", GSF_XML_2ND, NULL, NULL),
1275 			GSF_XML_IN_NODE (FRAGMENT, BOND, -1, "b", GSF_XML_CONTENT, cdxml_bond_start, cdxml_simple_end),
1276 		GSF_XML_IN_NODE (PAGE, GROUP, -1, "group", GSF_XML_CONTENT, cdxml_group_start, cdxml_simple_end),
1277 			GSF_XML_IN_NODE (GROUP, FRAGMENT, -1, "fragment", GSF_XML_2ND, NULL, NULL),
1278 		GSF_XML_IN_NODE (PAGE, GRAPHIC, -1, "graphic", GSF_XML_CONTENT, cdxml_graphic_start, NULL),
1279 		GSF_XML_IN_NODE (PAGE, ALTGROUP, -1, "altgroup", GSF_XML_CONTENT, NULL, NULL),
1280 		GSF_XML_IN_NODE (PAGE, CURVE, -1, "curve", GSF_XML_CONTENT, NULL, NULL),
1281 		GSF_XML_IN_NODE (PAGE, STEP, -1, "step", GSF_XML_NO_CONTENT, NULL, NULL),
1282 		GSF_XML_IN_NODE (PAGE, SCHEME, -1, "scheme", GSF_XML_NO_CONTENT, NULL, cdxml_scheme_end),
1283 			GSF_XML_IN_NODE (SCHEME, REACTIONSTEP, -1, "step", GSF_XML_CONTENT, cdxml_step_start, NULL),
1284 		GSF_XML_IN_NODE (PAGE, SPECTRUM, -1, "spectrum", GSF_XML_CONTENT, NULL, NULL),
1285 		GSF_XML_IN_NODE (PAGE, EMBEDDEDOBJECT, -1, "embeddedobject", GSF_XML_CONTENT, NULL, NULL),
1286 		GSF_XML_IN_NODE (PAGE, SEQUENCE, -1, "sequence", GSF_XML_CONTENT, NULL, NULL),
1287 		GSF_XML_IN_NODE (PAGE, CROSSREFERENCE, -1, "crossreference", GSF_XML_CONTENT, NULL, NULL),
1288 		GSF_XML_IN_NODE (PAGE, SPLITTER, -1, "splitter", GSF_XML_CONTENT, NULL, NULL),
1289 		GSF_XML_IN_NODE (PAGE, TABLE, -1, "table", GSF_XML_CONTENT, NULL, NULL),
1290 		GSF_XML_IN_NODE (PAGE, BRACKETEDGROUP, -1, "bracketedgroup", GSF_XML_CONTENT, NULL, NULL),
1291 		GSF_XML_IN_NODE (PAGE, BORDER, -1, "border", GSF_XML_CONTENT, NULL, NULL),
1292 		GSF_XML_IN_NODE (PAGE, GEOMETRY, -1, "geometry", GSF_XML_CONTENT, NULL, NULL),
1293 		GSF_XML_IN_NODE (PAGE, CONSTRAINT, -1, "constraint", GSF_XML_CONTENT, NULL, NULL),
1294 		GSF_XML_IN_NODE (PAGE, TLCPLATE, -1, "tlcplate", GSF_XML_CONTENT, NULL, NULL),
1295 		GSF_XML_IN_NODE (PAGE, CHEMICALPROPERTY, -1, "chemicalproperty", GSF_XML_CONTENT, NULL, NULL),
1296 		GSF_XML_IN_NODE (PAGE, ARROW, -1, "arrow", GSF_XML_CONTENT, NULL, NULL),
1297 		GSF_XML_IN_NODE (PAGE, BIOSHAPE, -1, "bioshape", GSF_XML_CONTENT, NULL, NULL),
1298 		GSF_XML_IN_NODE (PAGE, STOICHIOMETRY, -1, "stoichiometrygrid", GSF_XML_CONTENT, NULL, NULL),
1299 		GSF_XML_IN_NODE (PAGE, PLASMIDMAP, -1, "plasmidmap", GSF_XML_CONTENT, NULL, NULL),
1300 		GSF_XML_IN_NODE (PAGE, OBJECTTAG, -1, "objecttag", GSF_XML_CONTENT, NULL, NULL),
1301 		GSF_XML_IN_NODE (PAGE, ANNOTATION, -1, "annotation", GSF_XML_CONTENT, NULL, NULL),
1302 		GSF_XML_IN_NODE (PAGE, RLOGIC, -1, "rlogic", GSF_XML_CONTENT, NULL, NULL),
1303 	GSF_XML_IN_NODE (CDXML, TEMPLATEGRID, -1, "templategrid", GSF_XML_CONTENT, NULL, NULL),
1304 GSF_XML_IN_NODE_END
1305 };
1306 
Read(Document * doc,GsfInput * in,G_GNUC_UNUSED char const * mime_type,GOIOContext * io)1307 ContentType CDXMLLoader::Read  (Document *doc, GsfInput *in, G_GNUC_UNUSED char const *mime_type, GOIOContext *io)
1308 {
1309 	CDXMLReadState state;
1310 
1311 	state.doc = doc;
1312 	state.app = doc->GetApplication ();
1313 	state.context = io;
1314 	ContentType success = ContentTypeUnknown;
1315 	state.colors.push_back ("red=\"0\" green=\"0\" blue=\"0\""); // black
1316 	state.colors.push_back ("red=\"1\" green=\"1\" blue=\"1\""); // white
1317 	state.font = 0;
1318 	state.color = 0;
1319 	state.theme = NULL;
1320 	state.labelFont = -1;
1321 	state.captionFont = -1;
1322 
1323 	if (NULL != in) {
1324 		GsfXMLInDoc *xml = gsf_xml_in_doc_new (cdxml_dtd, NULL);
1325 		if (gsf_xml_in_doc_parse (xml, in, &state))
1326 			success = ContentType2D;
1327 
1328 		if (success == ContentTypeUnknown)
1329 			go_io_warning (state.context,
1330 				_("'%s' is corrupt!"),
1331 				gsf_input_name (in));
1332 		else if (!state.failed.empty ()) {
1333 			CDXMLProps p;
1334 			Object *parent = NULL;
1335 			while (!state.failed.empty ()) {
1336 				p = state.failed.front ();
1337 				if (parent != p.obj->GetParent ()) {
1338 					if (parent)
1339 						parent->OnLoaded ();
1340 					parent = p.obj->GetParent ();
1341 				}
1342 				if (!p.obj->SetProperty (p.property, p.value.c_str ())) {
1343 					success = ContentTypeUnknown;
1344 					go_io_warning (state.context,
1345 						_("'%s' is corrupt!"),
1346 						gsf_input_name (in));
1347 				}
1348 				state.failed.pop_front ();
1349 			}
1350 			if (parent)
1351 				parent->OnLoaded ();
1352 		}
1353 		gsf_xml_in_doc_free (xml);
1354 	}
1355 	// Update the view so that the positions are correct
1356 	static_cast <gcp::Document *> (doc)->GetView ()->Update (doc);
1357 	// now build schemes
1358 	std::list < SchemeData >::iterator i, iend = state.schemes.end ();
1359 	for (i = state.schemes.begin (); i != iend; i++) {
1360 		if ((*i).Steps.empty ())
1361 			continue;
1362 		build_scheme (state, *i);
1363 	}
1364 	return success;
1365 }
1366 
1367 ////////////////////////////////////////////////////////////////////////////////
1368 // Writing code
1369 
1370 static bool start = true;
cb_xml_to_vfs(GsfOutput * output,const guint8 * buf,int nb)1371 static int cb_xml_to_vfs (GsfOutput *output, const guint8* buf, int nb)
1372 {
1373 	if (start) {
1374 		char const *end = strchr (reinterpret_cast <char const *> (buf), '\n');
1375 		gsf_output_write (output, 40, reinterpret_cast <guint8 const *> ("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"));
1376 		gsf_output_write (output, 70, reinterpret_cast <guint8 const *> ("<!DOCTYPE CDXML SYSTEM \"http://www.cambridgesoft.com/xml/cdxml.dtd\" >\n"));
1377 		start = false;
1378 		return gsf_output_write (output, strlen (end) - 1, reinterpret_cast <guint8 const *> (end + 1))? nb: 0;
1379 	} else
1380 		return gsf_output_write (output, nb, buf)? nb: 0;
1381 }
1382 
WriteArrow(CDXMLLoader * loader,xmlDocPtr xml,xmlNodePtr parent,Object const * obj,GOIOContext * s)1383 bool CDXMLLoader::WriteArrow (CDXMLLoader *loader, xmlDocPtr xml, xmlNodePtr parent, Object const *obj, GOIOContext *s)
1384 {
1385 	std::map <std::string, Object *>::const_iterator i;
1386 	gcu::Object const *child = obj->GetFirstChild (i);
1387 	while (child) {
1388 		if (!loader->WriteObject (xml, parent, child, s))
1389 			return false;
1390 		child = obj->GetNextChild (i);
1391 	}
1392 	xmlNodePtr node = xmlNewDocNode (xml, NULL, reinterpret_cast <xmlChar const * > ("graphic"), NULL);
1393 	xmlAddChild (parent, node);
1394 	loader->m_SavedIds[obj->GetId ()] = loader->m_MaxId;
1395 	AddIntProperty (node, "id", loader->m_MaxId++);
1396 	std::istringstream in (obj->GetProperty (GCU_PROP_ARROW_COORDS));
1397 	double x0, y0, x1, y1;
1398 	in >> x0 >> y0 >> x1 >> y1;
1399 	std::ostringstream out;
1400 	out << x1 << " " << y1 << " " << x0 << " " << y0;
1401 	AddStringProperty (node, "BoundingBox", out.str ());
1402 	AddIntProperty (node, "Z", loader->m_Z++);
1403 	AddStringProperty (node, "GraphicType", "Line");
1404 	std::string type = gcu::Object::GetTypeName (obj->GetType ());
1405 	if (type == "reaction-arrow")
1406 		AddStringProperty (node, "ArrowType", (obj->GetProperty (GCU_PROP_REACTION_ARROW_TYPE) == "double")? "Equilibrium": "FullHead");
1407     else if (type == "mesomery-arrow")
1408 		AddStringProperty (node, "ArrowType", "Resonance");
1409 	else if (type == "retrosynthesis-arrow")
1410 		AddStringProperty (node, "ArrowType", "RetroSynthetic");
1411 	return true;
1412 }
1413 
WriteScheme(xmlDocPtr xml,xmlNodePtr parent,Object const * obj,std::string const & arrow_type,GOIOContext * s)1414 bool CDXMLLoader::WriteScheme (xmlDocPtr xml, xmlNodePtr parent, Object const *obj, std::string const &arrow_type, GOIOContext *s)
1415 {
1416 	std::map <std::string, Object *>::const_iterator i;
1417 	gcu::Object const *child = obj->GetFirstChild (i);
1418 	std::list < gcu::Object const * > arrows;
1419 	std::list < gcu::Object const * >::const_iterator it, itend;
1420 	while (child) {
1421 		std::string name = Object::GetTypeName (child->GetType ());
1422 		if (name == arrow_type)
1423 			arrows.push_back (child);
1424 		else if (!WriteObject (xml, parent, child, s))
1425 			return false;
1426 		child = obj->GetNextChild (i);
1427 	}
1428 	itend = arrows.end ();
1429 	for (it = arrows.begin (); it != itend; it++)
1430 		// save the graphic object.
1431 		if (!WriteArrow (this, xml, parent, *it, s))
1432 			return false;
1433 	if (!m_WriteScheme)
1434 		return true;
1435 	// Now, save the scheme
1436 	xmlNodePtr node = xmlNewDocNode (xml, NULL, reinterpret_cast <xmlChar const * > ("scheme"), NULL);
1437 	xmlAddChild (parent, node);
1438 	AddIntProperty (node, "id", m_MaxId++);
1439 	for (it = arrows.begin (); it != itend; it++) {
1440 		// save the associated step
1441 		xmlNodePtr step = xmlNewDocNode (xml, NULL, reinterpret_cast <xmlChar const * > ("step"), NULL);
1442 		xmlAddChild (node, step);
1443 		AddIntProperty (step, "id", m_MaxId++);
1444 		// reactants
1445 		gcu::Object const *arrow = *it;
1446 		gcu::Object const *cur = obj->GetDescendant (arrow->GetProperty (GCU_PROP_ARROW_START_ID).c_str ());
1447 		if (cur) {
1448 			child = cur->GetFirstChild (i);
1449 			if (child) {
1450 				std::ostringstream out;
1451 				out << m_SavedIds[child->GetId ()];
1452 				AddStringProperty (step, "ReactionStepReactants", out.str ());
1453 			}
1454 		}
1455 		// products
1456 		cur = obj->GetDescendant (arrow->GetProperty (GCU_PROP_ARROW_END_ID).c_str ());
1457 		if (cur) {
1458 			child = cur->GetFirstChild (i);
1459 			if (child) {
1460 				std::ostringstream out;
1461 				out << m_SavedIds[child->GetId ()];
1462 				AddStringProperty (step, "ReactionStepProducts", out.str ());
1463 			}
1464 		}
1465 		// arrow
1466 		AddIntProperty (step, "ReactionStepArrows", m_SavedIds[arrow->GetId ()]);
1467 	}
1468 	return true;
1469 }
1470 
WriteMesomery(CDXMLLoader * loader,xmlDocPtr xml,xmlNodePtr parent,Object const * obj,GOIOContext * s)1471 bool CDXMLLoader::WriteMesomery (CDXMLLoader *loader, xmlDocPtr xml, xmlNodePtr parent, Object const *obj, GOIOContext *s)
1472 {
1473 	return loader->WriteScheme (xml, parent, obj, "mesomery-arrow", s);
1474 }
1475 
WriteReactionStep(CDXMLLoader * loader,xmlDocPtr xml,xmlNodePtr parent,Object const * obj,GOIOContext * s)1476 bool CDXMLLoader::WriteReactionStep(CDXMLLoader *loader, xmlDocPtr xml, xmlNodePtr parent, Object const *obj, GOIOContext *s)
1477 {
1478 	std::map <std::string, Object *>::const_iterator i;
1479 	gcu::Object const *child = obj->GetFirstChild (i);
1480 	while (child) {
1481 		std::string name = Object::GetTypeName (child->GetType ());
1482 		if (name == "reaction-operator") {
1483 			xmlNodePtr node = xmlNewDocNode (xml, NULL, reinterpret_cast <xmlChar const * > ("graphic"), NULL);
1484 			xmlAddChild (parent, node);
1485 			loader->m_SavedIds[obj->GetId ()] = loader->m_MaxId;
1486 			AddIntProperty (node, "id", loader->m_MaxId++);
1487 			// Write the bounding box
1488 			std::istringstream str(child->GetProperty (GCU_PROP_POS2D));
1489 			double x, y;
1490 			str >> x >> y;
1491 			x -= loader->m_FontSize / 3;
1492 			y += loader->m_CHeight + loader->m_FontSize / 2;
1493 			std::ostringstream out;
1494 			out << x << " " << y - loader->m_FontSize << " " << x << " " << y;
1495 			AddStringProperty (node, "BoundingBox", out.str ());
1496 			AddIntProperty (node, "Z", loader->m_Z++);
1497 			AddStringProperty (node, "GraphicType", "Symbol");
1498 			AddStringProperty (node, "SymbolType", "Plus");
1499 		} else {
1500 			// search for a mesomery inside the reaction
1501 			std::string id = child->GetProperty (GCU_PROP_MOLECULE);
1502 			gcu::Object *mol = child->GetChild (id.c_str ());
1503 			if (gcu::Object::GetTypeName (mol->GetType ()) == "mesomery")
1504 				loader->m_WriteScheme = false;
1505 			if (!loader->WriteObject (xml, parent, child, s))
1506 				return false;
1507 		}
1508 		child = obj->GetNextChild (i);
1509 	}
1510 	return true;
1511 }
1512 
WriteReaction(CDXMLLoader * loader,xmlDocPtr xml,xmlNodePtr parent,Object const * obj,GOIOContext * s)1513 bool CDXMLLoader::WriteReaction (CDXMLLoader *loader, xmlDocPtr xml, xmlNodePtr parent, Object const *obj, GOIOContext *s)
1514 {
1515 	std::map <std::string, Object *>::const_iterator i;
1516 	gcu::Object const *child = obj->GetFirstChild (i);
1517 	std::list < gcu::Object const * > arrows;
1518 	std::list < gcu::Object const * >::const_iterator it, itend;
1519 	bool ok = true;
1520 	while (child) {
1521 		std::string name = Object::GetTypeName (child->GetType ());
1522 		if (name == "reaction-step") {
1523 			ok &= loader->WriteReactionStep (loader, xml, parent, child, s);
1524 		} else if (name == "reaction-arrow")
1525 			arrows.push_back (child);
1526 		child = obj->GetNextChild (i);
1527 	}
1528 	if (!ok)
1529 		return false;
1530 	itend = arrows.end ();
1531 	for (it = arrows.begin (); it != itend; it++)
1532 		// save the graphic object.
1533 		if (!WriteArrow (loader, xml, parent, *it, s))
1534 			return false;
1535 	// Now, save the reaction
1536 	if (loader->m_WriteScheme) {
1537 		xmlNodePtr node = xmlNewDocNode (xml, NULL, reinterpret_cast <xmlChar const * > ("scheme"), NULL);
1538 		xmlAddChild (parent, node);
1539 		AddIntProperty (node, "id", loader->m_MaxId++);
1540 		for (it = arrows.begin (); it != itend; it++) {
1541 			// save the associated step
1542 			xmlNodePtr step = xmlNewDocNode (xml, NULL, reinterpret_cast <xmlChar const * > ("step"), NULL);
1543 			xmlAddChild (node, step);
1544 			AddIntProperty (step, "id", loader->m_MaxId++);
1545 			std::list < unsigned > Ids, Ids_;
1546 			// FIXME what happens if the reaction step contains a mesomery?
1547 			// reactants
1548 			gcu::Object const *arrow = *it;
1549 			gcu::Object const *cur = obj->GetDescendant (arrow->GetProperty (GCU_PROP_ARROW_START_ID).c_str ());
1550 			if (cur) {
1551 				child = cur->GetFirstChild (i);
1552 				while (child) {
1553 					if (child->GetType () == gcu::ReactantType)
1554 						Ids.push_back (loader->m_SavedIds[child->GetProperty (GCU_PROP_MOLECULE)]);
1555 					child = cur->GetNextChild (i);
1556 				}
1557 				if (!Ids.empty ()) {
1558 					std::ostringstream out;
1559 					out << Ids.front ();
1560 					Ids.pop_front ();
1561 					while (!Ids.empty ()) {
1562 						out << " " << Ids.front ();
1563 						Ids.pop_front ();
1564 					}
1565 					AddStringProperty (step, "ReactionStepReactants", out.str ());
1566 				}
1567 			}
1568 			// products
1569 			cur = obj->GetDescendant (arrow->GetProperty (GCU_PROP_ARROW_END_ID).c_str ());
1570 			if (cur) {
1571 				child = cur->GetFirstChild (i);
1572 				while (child) {
1573 					if (child->GetType () == gcu::ReactantType)
1574 						Ids.push_back (loader->m_SavedIds[child->GetProperty (GCU_PROP_MOLECULE)]);
1575 					child = cur->GetNextChild (i);
1576 				}
1577 				if (!Ids.empty ()) {
1578 					std::ostringstream out;
1579 					out << Ids.front ();
1580 					Ids.pop_front ();
1581 					while (!Ids.empty ()) {
1582 						out << " " << Ids.front ();
1583 						Ids.pop_front ();
1584 					}
1585 					AddStringProperty (step, "ReactionStepProducts", out.str ());
1586 				}
1587 			}
1588 			// arrow
1589 			AddIntProperty (step, "ReactionStepArrows", loader->m_SavedIds[arrow->GetId ()]);
1590 			// attached objects
1591 			child = arrow->GetFirstChild (i);
1592 			double y = const_cast < gcu::Object * > (arrow)->GetYAlign ();
1593 			while (child) {
1594 				if (y > const_cast < gcu::Object * > (child)->GetYAlign ())
1595 					Ids_.push_back (loader->m_SavedIds[child->GetProperty (GCU_PROP_ARROW_OBJECT)]);
1596 				else
1597 					Ids.push_back (loader->m_SavedIds[child->GetProperty (GCU_PROP_ARROW_OBJECT)]);
1598 				child = arrow->GetNextChild (i);
1599 			}
1600 			// objects above the arrow
1601 			if (!Ids.empty ()) {
1602 				std::ostringstream out;
1603 				out << Ids.front ();
1604 				Ids.pop_front ();
1605 				while (!Ids.empty ()) {
1606 					out << " " << Ids.front ();
1607 					Ids.pop_front ();
1608 				}
1609 				AddStringProperty (step, "ReactionStepObjectsAboveArrow", out.str ());
1610 			}
1611 			// objects below the arrow
1612 			if (!Ids_.empty ()) {
1613 				std::ostringstream out;
1614 				out << Ids_.front ();
1615 				Ids_.pop_front ();
1616 				while (!Ids_.empty ()) {
1617 					out << " " << Ids_.front ();
1618 					Ids_.pop_front ();
1619 				}
1620 				AddStringProperty (step, "ReactionStepObjectsBelowArrow", out.str ());
1621 			}
1622 		}
1623 	}
1624 	loader->m_WriteScheme = true;
1625 	return true;
1626 }
1627 
WriteRetrosynthesis(CDXMLLoader * loader,xmlDocPtr xml,xmlNodePtr parent,Object const * obj,GOIOContext * s)1628 bool CDXMLLoader::WriteRetrosynthesis (CDXMLLoader *loader, xmlDocPtr xml, xmlNodePtr parent, Object const *obj, GOIOContext *s)
1629 {
1630 	return loader->WriteScheme (xml, parent, obj, "retrosynthesis-arrow", s);
1631 }
1632 
WriteAtom(CDXMLLoader * loader,xmlDocPtr xml,xmlNodePtr parent,Object const * obj,G_GNUC_UNUSED GOIOContext * s)1633 bool CDXMLLoader::WriteAtom (CDXMLLoader *loader, xmlDocPtr xml, xmlNodePtr parent, Object const *obj, G_GNUC_UNUSED GOIOContext *s)
1634 {
1635 	xmlNodePtr node = xmlNewDocNode (xml, NULL, reinterpret_cast <xmlChar const *> ("n"), NULL);
1636 	xmlAddChild (parent, node);
1637 	loader->m_SavedIds[obj->GetId ()] = loader->m_MaxId;
1638 	AddIntProperty (node, "id", loader->m_MaxId++);
1639 	string prop = obj->GetProperty (GCU_PROP_POS2D);
1640 	AddStringProperty (node, "p", prop);
1641 	AddIntProperty (node, "Z", loader->m_Z++);
1642 	prop = obj->GetProperty (GCU_PROP_ATOM_Z);
1643 	if (prop != "6")
1644 		AddStringProperty (node, "Element", prop);
1645 	prop = obj->GetProperty (GCU_PROP_TEXT_TEXT);
1646 	if (prop.length () > 0) {
1647 		xmlNodePtr text = xmlNewDocNode (xml, NULL, reinterpret_cast <xmlChar const *> ("t"), NULL);
1648 		xmlAddChild (node, text);
1649 		string prop2 = obj->GetProperty (GCU_PROP_TEXT_POSITION);
1650 		AddStringProperty (text, "p", prop2);
1651 		AddStringProperty (text, "LabelJustification", "Left");
1652 		AddStringProperty (text, "LabelAlignment", "Left");
1653 		xmlNodePtr sub = xmlNewDocNode (xml, NULL, reinterpret_cast <xmlChar const *> ("s"), NULL);
1654 		xmlAddChild (text, sub);
1655 		AddIntProperty (sub, "font", loader->m_LabelFont);
1656 		AddIntProperty (sub, "face", loader->m_LabelFontFace);
1657 		AddIntProperty (sub, "size", loader->m_LabelFontSize);
1658 		AddIntProperty (sub, "color", loader->m_LabelFontColor);
1659 		xmlNodeAddContent (sub, reinterpret_cast <xmlChar const *> (prop.c_str ()));
1660 	}
1661 	return true;
1662 }
1663 
WriteFragment(CDXMLLoader * loader,xmlDocPtr xml,xmlNodePtr parent,Object const * obj,G_GNUC_UNUSED GOIOContext * s)1664 bool CDXMLLoader::WriteFragment (CDXMLLoader *loader, xmlDocPtr xml, xmlNodePtr parent, Object const *obj, G_GNUC_UNUSED GOIOContext *s)
1665 {
1666 	xmlNodePtr node = xmlNewDocNode (xml, NULL, reinterpret_cast <xmlChar const *> ("n"), NULL);
1667 	xmlAddChild (parent, node);
1668 	loader->m_SavedIds[obj->GetId ()] = loader->m_MaxId;
1669 	std::string prop = obj->GetProperty (GCU_PROP_FRAGMENT_ATOM_ID);
1670 	gcu::Object *atom = obj->GetChild (prop.c_str ());
1671 	loader->m_SavedIds[atom->GetId ()] = loader->m_MaxId;
1672 	AddIntProperty (node, "id", loader->m_MaxId++);
1673 	prop = obj->GetProperty (GCU_PROP_POS2D);
1674 	AddStringProperty (node, "p", prop);
1675 	AddIntProperty (node, "Z", loader->m_Z++);
1676 	AddStringProperty (node, "NodeType", "Fragment");
1677 	prop = obj->GetProperty (GCU_PROP_TEXT_TEXT);
1678 	std::string pos = obj->GetProperty (GCU_PROP_FRAGMENT_ATOM_START);
1679 	unsigned as = atoi (pos.c_str ());
1680 	if (as > 0) {
1681 		char const *symbol = static_cast < gcu::Atom * > (atom)->GetSymbol ();
1682 		unsigned ae = as + strlen (symbol);
1683 		if (ae < prop.length () - 1) {
1684 			// attachment point is in the middle of the string, we need to bring it to start,
1685 			// and add put the left part inside brackets
1686 			std::string left = prop.substr (0, as), right = prop.substr (ae);
1687 			prop = symbol;
1688 			prop += "(";
1689 			gcu::Formula *formula = new gcu::Formula (left, GCU_FORMULA_PARSE_RESIDUE);
1690 			std::list < FormulaElt * > const &elts = formula->GetElements ();
1691 			std::list< FormulaElt * >::const_reverse_iterator i, end = elts.rend ();
1692 			for (i = elts.rbegin (); i!= end; i++)
1693 				prop += (*i)->Text ();
1694 			prop += ")";
1695 			prop += right;
1696 			delete formula;
1697 		} else {
1698 			// atom is at end, we need to revert the formula
1699 			gcu::Formula *formula = new gcu::Formula (prop, GCU_FORMULA_PARSE_RESIDUE);
1700 			std::list < FormulaElt * > const &elts = formula->GetElements ();
1701 			prop.clear ();
1702 			std::list< FormulaElt * >::const_reverse_iterator i, end = elts.rend ();
1703 			for (i = elts.rbegin (); i!= end; i++)
1704 				prop += (*i)->Text ();
1705 			delete formula;
1706 		}
1707 	}
1708 	if (prop.length () > 0) {
1709 		xmlNodePtr text = xmlNewDocNode (xml, NULL, reinterpret_cast <xmlChar const *> ("t"), NULL);
1710 		xmlAddChild (node, text);
1711 		string prop2 = obj->GetProperty (GCU_PROP_TEXT_POSITION);
1712 		AddStringProperty (text, "p", prop2);
1713 		AddStringProperty (text, "LabelJustification", "Left");
1714 		AddStringProperty (text, "LabelAlignment", "Left");
1715 		xmlNodePtr sub = xmlNewDocNode (xml, NULL, reinterpret_cast <xmlChar const *> ("s"), NULL);
1716 		xmlAddChild (text, sub);
1717 		AddIntProperty (sub, "font", loader->m_LabelFont);
1718 		AddIntProperty (sub, "face", loader->m_LabelFontFace);
1719 		AddIntProperty (sub, "size", loader->m_LabelFontSize);
1720 		AddIntProperty (sub, "color", loader->m_LabelFontColor);
1721 		xmlNodeAddContent (sub, reinterpret_cast <xmlChar const *> (prop.c_str ()));
1722 	}
1723 	return true;
1724 }
1725 
WriteBond(CDXMLLoader * loader,xmlDocPtr xml,xmlNodePtr parent,Object const * obj,G_GNUC_UNUSED GOIOContext * s)1726 bool CDXMLLoader::WriteBond (CDXMLLoader *loader, xmlDocPtr xml, xmlNodePtr parent, Object const *obj, G_GNUC_UNUSED GOIOContext *s)
1727 {
1728 	xmlNodePtr node = xmlNewDocNode (xml, NULL, reinterpret_cast <xmlChar const *> ("b"), NULL);
1729 	xmlAddChild (parent, node);
1730 	loader->m_SavedIds[obj->GetId ()] = loader->m_MaxId;
1731 	AddIntProperty (node, "id", loader->m_MaxId++);
1732 	AddIntProperty (node, "Z", loader->m_Z++);
1733 	string prop = obj->GetProperty (GCU_PROP_BOND_BEGIN);
1734 	AddIntProperty (node, "B", loader->m_SavedIds[prop]);
1735 	prop = obj->GetProperty (GCU_PROP_BOND_END);
1736 	AddIntProperty (node, "E", loader->m_SavedIds[prop]);
1737 	prop = obj->GetProperty (GCU_PROP_BOND_ORDER);
1738 	if (prop == "3")
1739 		prop = "4";
1740 	else if (prop != "2")
1741 		prop.clear ();
1742 	if (prop.length ())
1743 		AddStringProperty (node, "Order", prop);
1744 	prop = obj->GetProperty (GCU_PROP_BOND_TYPE);
1745 	if (prop == "wedge")
1746 		prop = "WedgeBegin";
1747 	else if (prop == "hash")
1748 		prop = "WedgedHashBegin";
1749 	else if (prop == "squiggle")
1750 		prop = "Wavy";
1751 	else if (prop == "large")
1752 		prop = "Bold";
1753 	else
1754 		prop.clear ();
1755 	if (prop.length ())
1756 		AddStringProperty (node, "Display", prop);
1757 	prop = obj->GetProperty (GCU_PROP_BOND_DOUBLE_POSITION);
1758 	if (prop == "center")
1759 		AddStringProperty (node, "DoublePosition", "Center");
1760 	else if (prop == "right")
1761 		AddStringProperty (node, "DoublePosition", "Right");
1762 	else if (prop == "left")
1763 		AddStringProperty (node, "DoublePosition", "Left");
1764 	return true;
1765 }
1766 
WriteMolecule(CDXMLLoader * loader,xmlDocPtr xml,xmlNodePtr parent,Object const * obj,GOIOContext * s)1767 bool CDXMLLoader::WriteMolecule (CDXMLLoader *loader, xmlDocPtr xml, xmlNodePtr parent, Object const *obj, GOIOContext *s)
1768 {
1769 	xmlNodePtr node = xmlNewDocNode (xml, NULL, reinterpret_cast <xmlChar const *> ("fragment"), NULL);
1770 	xmlAddChild (parent, node);
1771 	loader->m_SavedIds[obj->GetId ()] = loader->m_MaxId;
1772 	AddIntProperty (node, "id", loader->m_MaxId++);
1773 	// save atoms
1774 	std::map <std::string, Object *>::const_iterator i;
1775 	Object const *child = obj->GetFirstChild (i);
1776 	while (child) {
1777 		if (child->GetType () == AtomType && !loader->WriteObject (xml, node, child, s))
1778 			return false;
1779 		child = obj->GetNextChild (i);
1780 	}
1781 	// save fragments
1782 	child = obj->GetFirstChild (i);
1783 	while (child) {
1784 		if (child->GetType () == FragmentType && !loader->WriteObject (xml, node, child, s))
1785 			return false;
1786 		child = obj->GetNextChild (i);
1787 	}
1788 	// save bonds
1789 	child = obj->GetFirstChild (i);
1790 	while (child) {
1791 		if (child->GetType () == BondType && !loader->WriteObject (xml, node, child, s))
1792 			return false;
1793 		child = obj->GetNextChild (i);
1794 	}
1795 	return true;
1796 }
1797 
WriteNode(xmlNodePtr node,WriteTextState * state)1798 bool CDXMLLoader::WriteNode (xmlNodePtr node, WriteTextState *state)
1799 {
1800 	std::string name (reinterpret_cast < char const * > (node->name));
1801 	if (name == "br" || name == "text") {
1802 		if (state->node == NULL) {
1803 			state->node = xmlNewDocNode (state->xml, NULL, reinterpret_cast < xmlChar const * > ("s"),
1804 			                             (name == "br")? reinterpret_cast < xmlChar const * > ("s\n"): xmlNodeGetContent (node));
1805 			xmlAddChild (state->parent, state->node);
1806 			AddIntProperty (state->node, "font", state->font);
1807 			AddIntProperty (state->node, "size", state->size);
1808 			AddIntProperty (state->node, "color", state->color);
1809 			unsigned font_type = 0;
1810 			if (state->bold)
1811 				font_type |= 1;
1812 			if (state->italic)
1813 				font_type |= 2;
1814 			if (state->underline)
1815 				font_type |= 4;
1816 			switch (state->position) {
1817 			case -1:
1818 				font_type |= 0x20;
1819 				break;
1820 			case 1:
1821 				font_type |= 0x40;
1822 				break;
1823 			default:
1824 				break;
1825 			}
1826 			if (font_type != 0)
1827 				AddIntProperty (state->node, "face", font_type);
1828 		} else
1829 			xmlNodeAddContent (state->node, (name == "br")? reinterpret_cast < xmlChar const * > ("\n"): xmlNodeGetContent (node));
1830 		return true;
1831 	} else
1832 		state->node = NULL;
1833 	WriteTextState child_state = *state;
1834 	if (name == "i")
1835 		child_state.italic = true;
1836 	else if (name == "b")
1837 		child_state.bold = true;
1838 	else if (name == "u")
1839 		child_state.underline = true;
1840 	else if (name == "font") {
1841 		xmlChar *buf = xmlGetProp (node, reinterpret_cast < xmlChar const * > ("name"));
1842 		PangoFontDescription *desc = pango_font_description_from_string (reinterpret_cast < char * > (buf));
1843 		xmlFree (buf);
1844 		child_state.size = static_cast < double > (pango_font_description_get_size (desc)) / PANGO_SCALE;
1845 		std::string family = pango_font_description_get_family (desc);
1846 		if (family == "Arial")
1847 			child_state.font = 3;
1848 		else if (family == "Times New Roman")
1849 			child_state.font = 4;
1850 		else {
1851 			guint16 i = 5;
1852 			std::map < unsigned, CDXMLFont >::iterator it, itend = m_Fonts.end ();
1853 			for (it = m_Fonts.find (i); it != itend; it++, i++)
1854 				if (family == (*it).second.name)
1855 					break;
1856 			if (it == itend)
1857 				m_Fonts[i] = (CDXMLFont) {i, "iso-10646", family};
1858 			child_state.font = i;
1859 		}
1860 	} else if (name == "sub")
1861 		child_state.position = -1;
1862 	else if (name == "sup")
1863 		child_state.position = 1;
1864 	else if (name == "fore") {
1865 		GOColor color = ReadColor (node);
1866 		guint i = 2;
1867 		std::map < unsigned, GOColor >::iterator it, itend = m_Colors.end ();
1868 			for (it = m_Colors.find (i); it != itend; it++, i++)
1869 				if (color == (*it).second)
1870 					break;
1871 			if (it == itend)
1872 				m_Colors[i] = color;
1873 		child_state.color = i;
1874 
1875 	}
1876 	xmlNodePtr child = node->children;
1877 	while (child) {
1878 		WriteNode (child, &child_state);
1879 		child = child->next;
1880 	}
1881 	return true;
1882 }
1883 
WriteText(CDXMLLoader * loader,xmlDocPtr xml,xmlNodePtr parent,Object const * obj,GOIOContext * s)1884 bool CDXMLLoader::WriteText(CDXMLLoader *loader, xmlDocPtr xml, xmlNodePtr parent, Object const *obj, GOIOContext *s)
1885 {
1886 	xmlNodePtr node = xmlNewDocNode (xml, NULL, reinterpret_cast <xmlChar const *> ("t"), NULL);
1887 	xmlAddChild (parent, node);
1888 	loader->m_SavedIds[obj->GetId ()] = loader->m_MaxId;
1889 	AddIntProperty (node, "id", loader->m_MaxId++);
1890 	std::string prop = obj->GetProperty (GCU_PROP_POS2D);
1891 	if (prop.length ()) {
1892 		istringstream str (prop);
1893 		double x, y;
1894 		str >> x >> y;
1895 		y += loader->m_CHeight;
1896 		std::ostringstream out;
1897 		out << x << " " << y;
1898 		AddStringProperty (node, "p", out.str ());
1899 	}
1900 	AddIntProperty (node, "Z", loader->m_Z++);
1901 	// no need to export left justification or alignment.
1902 	prop = obj->GetProperty (GCU_PROP_TEXT_ALIGNMENT);
1903 	if (prop == "right")
1904 		AddStringProperty (node, "Justification", "Right");
1905 	else if (prop == "center")
1906 		AddStringProperty (node, "Justification", "Center");
1907 	prop = obj->GetProperty (GCU_PROP_TEXT_JUSTIFICATION);
1908 	if (prop == "right")
1909 		AddStringProperty (node, "CaptionJustification", "Right");
1910 	else if (prop == "center")
1911 		AddStringProperty (node, "CaptionJustification", "Center");
1912 	else if (prop == "justify")
1913 		AddStringProperty (node, "CaptionJustification", "Full");
1914 	double inl;
1915 	std::istringstream in (obj->GetProperty (GCU_PROP_TEXT_INTERLINE));
1916 	in >> inl;
1917 	if (inl <= 0.) {
1918 		prop = obj->GetProperty (GCU_PROP_TEXT_VARIABLE_LINE_HEIGHT);
1919 		AddStringProperty (node, "CaptionLineHeight", (prop =="true")? "variable": "auto");
1920 	} else {
1921 		std::istringstream in (obj->GetProperty (GCU_PROP_TEXT_MAX_LINE_HEIGHT));
1922 		double lh;
1923 		in >> lh;
1924 		AddIntProperty (node, "CaptionLineHeight", inl + lh);
1925 	}
1926 	prop = obj->GetProperty (GCU_PROP_TEXT_MARKUP);
1927 	xmlDocPtr doc = xmlParseMemory (prop.c_str(), prop.length ());
1928 	xmlNodePtr text_node = doc->children->children;
1929 	WriteTextState state;
1930 	state.xml = xml;
1931 	state.node = NULL;
1932 	state.parent = node;
1933 	state.s = s;
1934 	state.italic = false;
1935 	state.bold = false;
1936 	state.underline = false;
1937 	state.font = 3;
1938 	state.size = 10;
1939 	state.position = 0;
1940 	state.color = 3;
1941 	while (text_node) {
1942 		if (strcmp (reinterpret_cast < char const *>(text_node->name), "position"))
1943 			loader->WriteNode (text_node, &state);
1944 		text_node = text_node->next;
1945 	}
1946 	xmlFreeDoc (doc);
1947 	return true;
1948 }
1949 
WriteObject(xmlDocPtr xml,xmlNodePtr node,Object const * object,GOIOContext * io)1950 bool CDXMLLoader::WriteObject (xmlDocPtr xml, xmlNodePtr node, Object const *object, GOIOContext *io)
1951 {
1952 	string name = Object::GetTypeName (object->GetType ());
1953 	map <string, bool (*) (CDXMLLoader *, xmlDocPtr, xmlNodePtr, Object const *, GOIOContext *)>::iterator i = m_WriteCallbacks.find (name);
1954 	if (i != m_WriteCallbacks.end ())
1955 		return (*i).second (this, xml, node, object, io);
1956 	// if we don't save the object iself, try to save its children
1957 	std::map <std::string, Object *>::const_iterator j;
1958 	Object const *child = object->GetFirstChild (j);
1959 	while (child) {
1960 		if (!WriteObject (xml, node, child, io))
1961 			return false;
1962 		child = object->GetNextChild (j);
1963 	}
1964 	return true; /* loosing data is not considered an error, it is just a missing feature
1965 					either in this code or in the cml schema */
1966 }
1967 
AddIntProperty(xmlNodePtr node,char const * id,int value)1968 void CDXMLLoader::AddIntProperty (xmlNodePtr node, char const *id, int value)
1969 {
1970 	char *buf = g_strdup_printf ("%d", value);
1971 	xmlNewProp (node, reinterpret_cast <xmlChar const *> (id), reinterpret_cast <xmlChar *> (buf));
1972 	g_free (buf);
1973 }
AddFloatProperty(xmlNodePtr node,char const * id,double value)1974 void CDXMLLoader::AddFloatProperty (xmlNodePtr node, char const *id, double value)
1975 {
1976 	std::ostringstream s;
1977 	s << value;
1978 	xmlNewProp (node, reinterpret_cast <xmlChar const *> (id), reinterpret_cast <xmlChar const *> (s.str ().c_str ()));
1979 }
1980 
AddStringProperty(xmlNodePtr node,char const * id,string const & value)1981 void CDXMLLoader::AddStringProperty (xmlNodePtr node, char const *id, string const &value)
1982 {
1983 	xmlNewProp (node, reinterpret_cast <xmlChar const *> (id), reinterpret_cast <xmlChar const *> (value.c_str ()));
1984 }
1985 
Write(Object const * obj,GsfOutput * out,G_GNUC_UNUSED char const * mime_type,G_GNUC_UNUSED GOIOContext * io,G_GNUC_UNUSED ContentType type)1986 bool CDXMLLoader::Write  (Object const *obj, GsfOutput *out, G_GNUC_UNUSED char const *mime_type, G_GNUC_UNUSED GOIOContext *io, G_GNUC_UNUSED ContentType type)
1987 {
1988 	map<string, CDXMLFont> fonts;
1989 	// FIXME: should be able to export a molecule or a reaction
1990 	Document const *doc = dynamic_cast <Document const *> (obj);
1991 	xmlNodePtr colors, fonttable, page;
1992 	if (!doc || !out)
1993 		return false;
1994 
1995 	m_MaxId = m_Z = 1;
1996 
1997 	// Init default colors
1998 	m_Colors[2] = GO_COLOR_WHITE;
1999 	m_Colors[3] = GO_COLOR_BLACK;
2000 	m_Colors[4] = GO_COLOR_RED;
2001 	m_Colors[5] = GO_COLOR_YELLOW;
2002 	m_Colors[6] = GO_COLOR_GREEN;
2003 	m_Colors[7] = GO_COLOR_CYAN;
2004 	m_Colors[8] = GO_COLOR_BLUE;
2005 	m_Colors[9] = GO_COLOR_VIOLET;
2006 
2007 	// Init fonts, we always use Unknown as the charset, hoping it is not an issue
2008 	m_Fonts[3] = (CDXMLFont) {3, string ("iso-8859-1"), string ("Arial")};
2009 	m_Fonts[4] = (CDXMLFont) {4, string ("iso-8859-1"), string ("Times New Roman")};
2010 	m_LabelFont = 3;
2011 	m_LabelFontSize = 10.;
2012 
2013 	/* we can't use sax, because we need colors and fonts */
2014 	xmlDocPtr xml = xmlNewDoc (reinterpret_cast <xmlChar const *> ("1.0"));
2015 	xmlDocSetRootElement (xml,  xmlNewDocNode (xml, NULL, reinterpret_cast <xmlChar const *> ("CDXML"), NULL));
2016 	std::string str = doc->GetApp ()->GetName () + " " VERSION;
2017 	xmlNewProp (xml->children, reinterpret_cast <xmlChar const *> ("CreationProgram"),
2018 	            reinterpret_cast <xmlChar const *> (str.c_str ()));
2019 	// Get the theme (we need a gcp::Document there)
2020 	gcp::Document const *cpDoc = dynamic_cast < gcp::Document const * > (doc);
2021 	gcp::Theme const *theme = cpDoc->GetTheme ();
2022 	m_Zoom = 1. / theme->GetZoomFactor();
2023 	m_Scale = .75 / m_Zoom;
2024 	m_CHeight = const_cast < gcp::Document * > (cpDoc)->GetView ()->GetCHeight ();
2025 	// save title authors and the like
2026 	str = doc->GetProperty (GCU_PROP_DOC_CREATOR);
2027 	if (str.length () > 0)
2028 		AddStringProperty (xml->children, "CreationUserName", str);
2029 /* Don't write dates for now since the needed format is unknown
2030 	str = doc->GetProperty (GCU_PROP_DOC_CREATION_TIME);
2031 	unsigned d, m, Y;
2032 	if (sscanf (str.c_str (), "%u-%u-%u", &Y,&m, &d) == 3) {
2033 		GDateTime *date = g_date_time_new_utc (Y, m, d, 0, 0, 0.);
2034 		AddDateProperty (out, "CreationDate", date);
2035 		g_date_time_unref (date);
2036 	}
2037 	GDateTime *dt = g_date_time_new_now_utc ();
2038 	AddDateProperty (out, "ModificationDate", dt);
2039 	g_date_time_unref (dt);
2040 */
2041 	str = doc->GetProperty (GCU_PROP_DOC_TITLE);
2042 	if (str.length () > 0)
2043 		AddStringProperty (xml->children, "Name", str);
2044 	str = doc->GetProperty (GCU_PROP_DOC_COMMENT);
2045 	if (str.length () > 0)
2046 		AddStringProperty (xml->children, "Comment", str);
2047 	// determine the bond length and scale the document appropriately
2048 	const_cast <Document *> (doc)->SetScale (1. / m_Scale);
2049 	double l = theme->GetBondLength () * m_Scale;
2050 	AddFloatProperty (xml->children, "BondLength", l);
2051 	int n = theme->GetBondDist () * 100. * m_Zoom / theme->GetBondLength ();
2052 	AddIntProperty (xml->children, "BondSpacing", n);
2053 	l = theme->GetBondWidth () * .75;
2054 	AddFloatProperty (xml->children, "LineWidth", l);
2055 	l = theme->GetStereoBondWidth () * .75;
2056 	AddFloatProperty (xml->children, "BoldWidth", l);
2057 	l = theme->GetHashDist () * .75;
2058 	AddFloatProperty (xml->children, "HashSpacing", l);
2059 	AddFloatProperty (xml->children, "ChainAngle", theme->GetBondAngle ());
2060 	l = theme->GetPadding () * .75;
2061 	AddFloatProperty (xml->children, "MarginWidth", l);
2062 	str = theme->GetTextFontFamily ();
2063 	if (str == "Arial")
2064 		n = 3;
2065 	else if (str == "Times New Roman")
2066 		n = 4;
2067 	else {
2068 		n = 5;
2069 		std::map < unsigned, CDXMLFont >::iterator it, itend = m_Fonts.end ();
2070 		for (it = m_Fonts.find (n); it != itend; it++, n++)
2071 			if (str == (*it).second.name)
2072 				break;
2073 		if (it == itend)
2074 			m_Fonts[n] = (CDXMLFont) {static_cast < guint16 > (n), "iso-10646", str};
2075 	}
2076 	AddIntProperty (xml->children, "CaptionFont", n);
2077 	n = theme->GetTextFontSize () / PANGO_SCALE;
2078 	m_FontSize = n * .75;
2079 	AddIntProperty (xml->children, "CaptionSize", n);
2080 	n = 0;
2081 	if (theme->GetTextFontWeight () > PANGO_WEIGHT_NORMAL)
2082 		n |= 1;
2083 	if (theme->GetTextFontStyle () != PANGO_STYLE_NORMAL)
2084 		n |= 2;
2085 	AddIntProperty (xml->children, "CaptionFace", n);
2086 	str = theme->GetFontFamily ();
2087 	if (str == "Arial")
2088 		m_LabelFont = 3;
2089 	else if (str == "Times New Roman")
2090 		m_LabelFont = 4;
2091 	else {
2092 		m_LabelFont = 5;
2093 		std::map < unsigned, CDXMLFont >::iterator it, itend = m_Fonts.end ();
2094 		for (it = m_Fonts.find (m_LabelFont); it != itend; it++, m_LabelFont++)
2095 			if (str == (*it).second.name)
2096 				break;
2097 		if (it == itend)
2098 			m_Fonts[m_LabelFont] = (CDXMLFont) {static_cast < guint16 > (m_LabelFont), "iso-10646", str};
2099 	}
2100 	AddIntProperty (xml->children, "LabelFont", m_LabelFont);
2101 	m_LabelFontSize = theme->GetFontSize () / PANGO_SCALE;
2102 	AddIntProperty (xml->children, "LabelSize", m_LabelFontSize);
2103 	m_LabelFontFace = 0x60;
2104 	if (theme->GetFontWeight () > PANGO_WEIGHT_NORMAL)
2105 		m_LabelFontFace |= 1;
2106 	if (theme->GetFontStyle () != PANGO_STYLE_NORMAL)
2107 		m_LabelFontFace |= 2;
2108 	AddIntProperty (xml->children, "LabelFace", m_LabelFontFace);
2109 	m_LabelFontColor = 0;
2110 	AddIntProperty (xml->children, "CaptionJustification", 0);
2111 	// add color table
2112 	colors = xmlNewDocNode (xml, NULL, reinterpret_cast <xmlChar const *> ("colortable"), NULL);
2113 	xmlAddChild (xml->children, colors);
2114 	// add font table
2115 	fonttable = xmlNewDocNode (xml, NULL, reinterpret_cast <xmlChar const *> ("fonttable"), NULL);
2116 	xmlAddChild (xml->children, fonttable);
2117 	// start page
2118 	page = xmlNewDocNode (xml, NULL, reinterpret_cast <xmlChar const *> ("page"), NULL);
2119 	xmlAddChild (xml->children, page);
2120 	// build tree from children
2121 	std::map <std::string, Object *>::const_iterator i;
2122 	Object const *child = doc->GetFirstChild (i);
2123 	while (child) {
2124 		if (!WriteObject (xml, page, child, io)) {
2125 			xmlFreeDoc (xml);
2126 			m_Colors.clear ();
2127 			m_Fonts.clear ();
2128 			m_SavedIds.clear ();
2129 			return false;
2130 		}
2131 		child = doc->GetNextChild (i);
2132 	}
2133 	// add colors to color table
2134 	map <unsigned, GOColor>::iterator color, end_color = m_Colors.end ();
2135 	for (color = m_Colors.begin (); color != end_color; color++) {
2136 		xmlNodePtr node = xmlNewDocNode (xml, NULL, reinterpret_cast <xmlChar const *> ("color"), NULL);
2137 		xmlAddChild (colors, node);
2138 		WriteFloat (node, "r", GO_COLOR_DOUBLE_R ((*color).second));
2139 		WriteFloat (node, "g", GO_COLOR_DOUBLE_G ((*color).second));
2140 		WriteFloat (node, "b", GO_COLOR_DOUBLE_B ((*color).second));
2141 	}
2142 	// write fonts
2143 	map <unsigned, CDXMLFont>::iterator font, end_font = m_Fonts.end ();
2144 	for (font = m_Fonts.begin (); font != end_font; font++) {
2145 		xmlNodePtr node = xmlNewDocNode (xml, NULL, reinterpret_cast <xmlChar const *> ("font"), NULL);
2146 		xmlAddChild (fonttable, node);
2147 		WriteInt (node, "id", (*font).second.index);
2148 		AddStringProperty (node, "charset", (*font).second.encoding);
2149 		AddStringProperty (node, "name", (*font).second.name);
2150 	}
2151 	xmlIndentTreeOutput = true;
2152 	xmlKeepBlanksDefault (0);
2153 	xmlOutputBufferPtr buf = xmlAllocOutputBuffer (NULL);
2154 	buf->context = out;
2155 	buf->closecallback = NULL;
2156 	buf->writecallback = (xmlOutputWriteCallback) cb_xml_to_vfs;
2157 	start = true;
2158 	xmlSaveFormatFileTo (buf, xml, NULL, true);
2159 	xmlFreeDoc (xml);
2160 	m_Colors.clear ();
2161 	m_Fonts.clear ();
2162 	m_SavedIds.clear ();
2163 	return true;
2164 }
2165 
2166 ////////////////////////////////////////////////////////////////////////////////
2167 // Initialization
2168 
2169 static CDXMLLoader loader;
2170 
2171 extern "C" {
2172 
2173 extern GOPluginModuleDepend const go_plugin_depends [] = {
2174     { "goffice", GOFFICE_API_VERSION }
2175 };
2176 extern GOPluginModuleHeader const go_plugin_header =
2177 	{ GOFFICE_MODULE_PLUGIN_MAGIC_NUMBER, G_N_ELEMENTS (go_plugin_depends) };
2178 
2179 G_MODULE_EXPORT void
go_plugin_init(G_GNUC_UNUSED GOPlugin * plugin,G_GNUC_UNUSED GOCmdContext * cc)2180 go_plugin_init (G_GNUC_UNUSED GOPlugin *plugin, G_GNUC_UNUSED GOCmdContext *cc)
2181 {
2182 	bindtextdomain (GETTEXT_PACKAGE, DATADIR"/locale");
2183 	bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
2184 }
2185 
2186 G_MODULE_EXPORT void
go_plugin_shutdown(G_GNUC_UNUSED GOPlugin * plugin,G_GNUC_UNUSED GOCmdContext * cc)2187 go_plugin_shutdown (G_GNUC_UNUSED GOPlugin *plugin, G_GNUC_UNUSED GOCmdContext *cc)
2188 {
2189 }
2190 
2191 }
2192