1 /*
2  * part-property.c
3  *
4  *
5  * Authors:
6  *  Richard Hult <rhult@hem.passagen.se>
7  *  Ricardo Markiewicz <rmarkie@fi.uba.ar>
8  *  Andres de Barbara <adebarbara@fi.uba.ar>
9  *  Marc Lorber <lorber.marc@wanadoo.fr>
10  *
11  * Web page: https://ahoi.io/project/oregano
12  *
13  * Copyright (C) 1999-2001  Richard Hult
14  * Copyright (C) 2003,2006  Ricardo Markiewicz
15  * Copyright (C) 2009-2012  Marc Lorber
16  *
17  * This program is free software; you can redistribute it and/or
18  * modify it under the terms of the GNU General Public License as
19  * published by the Free Software Foundation; either version 2 of the
20  * License, or (at your option) any later version.
21  *
22  * This program is distributed in the hope that it will be useful,
23  * but WITHOUT ANY WARRANTY; without even the implied warranty of
24  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
25  * General Public License for more details.
26  *
27  * You should have received a copy of the GNU General Public
28  * License along with this program; if not, write to the
29  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
30  * Boston, MA 02110-1301, USA.
31  */
32 
33 #include <glib.h>
34 #include <string.h>
35 
36 #include "part.h"
37 #include "part-property.h"
38 
39 // Gets the name of a macro variable.
40 //
41 // @param str  str
42 // @param cls1 returns first conditional clause
43 // @param cls2 returns second clause
44 // @param sz   returns number of characters parsed
45 // @return the name of a macro variable
get_macro_name(char macro,const char * str,char ** cls1,char ** cls2,size_t * sz)46 static char *get_macro_name (char macro, const char *str, char **cls1, char **cls2, size_t *sz)
47 {
48 	char separators[] = {",.;/|()"};
49 	GString *out;
50 	const char *q, *qend;
51 	char *csep = NULL;
52 	size_t sln;
53 	int rc = 0;
54 	char *ret;
55 
56 	sln = strlen (str) + 1;
57 	*sz = 0;
58 	*cls1 = *cls2 = NULL;
59 	qend = str + sln;
60 	out = g_string_sized_new (sln);
61 
62 	// Get the name
63 	for (q = str; (*q) && (*q != ' ') && !(csep = strchr (separators, *q)); q++) {
64 		if (q > qend) {
65 			g_warning ("Expand macro error.");
66 			rc = 1;
67 			break;
68 		}
69 		out = g_string_append_c (out, *q);
70 	}
71 
72 	// if error found, return here
73 	if (rc)
74 		goto error;
75 
76 	// Look for conditional clauses
77 	if (csep && macro != '@' && macro != '&') {
78 		// get the first one
79 		GString *aux;
80 		q++; // skip the separator and store the clause in tmp
81 		aux = g_string_new ("");
82 		for (; (*q) && (*q != *csep); q++)
83 			g_string_append_c (aux, *q);
84 
85 		if (!*q) {
86 			g_string_free (aux, TRUE);
87 			goto error;
88 		}
89 
90 		*cls1 = aux->str;
91 		q++; // skip the end-of-clause separator
92 		g_string_free (aux, FALSE);
93 
94 		// Check for the second one
95 		if ((*q) && (csep = strchr (separators, *q))) {
96 			q++; // skip the separator and store in tmp
97 			aux = g_string_new ("");
98 			for (; (*q) && (*q != *csep); q++)
99 				g_string_append_c (aux, *q);
100 
101 			if (!(*q)) {
102 				g_free (*cls1);
103 				*cls1 = NULL;
104 				goto error;
105 			}
106 
107 			*cls2 = aux->str;
108 			q++; // skip the end-of-clause separator
109 			g_string_free (aux, FALSE);
110 		}
111 	}
112 
113 	*sz = out->len + (*cls1 != NULL ? strlen(*cls1) + 2 : 0) + (*cls2 != NULL ? strlen(*cls2) + 2 : 0);
114 	ret = NULL;
115 	if (out->len > 0) {
116 		out = g_string_append_c (out, '\0');
117 		ret = g_strdup (out->str);
118 	}
119 	g_string_free (out, TRUE);
120 
121 	return ret;
122 
123 error:
124 	g_string_free (out, TRUE);
125 	return NULL;
126 }
127 
128 // Rules:
129 // @<id>             value of <id>. If no value, error
130 // &<id>             value of <id> if <id> is defined
131 // ?<id>s...s        text between s...s separators if <id> defined
132 // ?<id>s...ss...s   text between 1st s...s separators if <id> defined
133 // else 2nd s...s clause
134 // ~<id>s...s        text between s...s separators if <id> undefined
135 // ~<id>s...ss...s   text between 1st s...s separators if <id> undefined
136 // else 2nd s...s clause
137 // #<id>s...s        text between s...s separators if <id> defined, but
138 // delete rest of template if <id> undefined
139 
140 // Separators can be any of {',', '.', ';', '/', '|', '(', ')'}.
141 // For an opening-closing pair of
142 // separators the same character has to be used.
143 
144 // Examples:
145 // R^@refdes %1 %2 @value
146 // V^@refdes %+ %- SIN(@offset @ampl @freq 0 0)
147 // ?DC|DC @DC|
part_property_expand_macros(Part * part,char * string)148 char *part_property_expand_macros (Part *part, char *string)
149 {
150 	static char mcode[] = {"@?~#&"};
151 	char *value;
152 	char *tmp0, *temp, *qn, *q0, *t0;
153 	char *cls1, *cls2;
154 	GString *out;
155 	size_t sln;
156 	char *ret;
157 
158 	g_return_val_if_fail (part != NULL, NULL);
159 	g_return_val_if_fail (IS_PART (part), NULL);
160 	g_return_val_if_fail (string != NULL, NULL);
161 
162 	cls1 = cls2 = q0 = NULL;
163 
164 	tmp0 = temp = g_strdup (string);
165 
166 	out = g_string_new ("");
167 
168 	for (temp = string; *temp;) {
169 		// Look for any of the macro char codes.
170 		if (strchr (mcode, *temp)) {
171 			qn = get_macro_name (*temp, temp + 1, &cls1, &cls2, &sln);
172 			if (qn == NULL)
173 				return NULL;
174 			value = part_get_property (part, qn);
175 			if ((*temp == '@' || *temp == '&') && value) {
176 				out = g_string_append (out, value);
177 			} else if (*temp == '&' && !value) {
178 				g_warning ("expand macro error: macro %s undefined", qn);
179 				g_free (qn);
180 				return NULL;
181 			} else if (*temp == '?' || *temp == '~') {
182 				if (cls1 == NULL) {
183 					g_warning ("error in template: %s", temp);
184 					g_free (qn);
185 					return NULL;
186 				}
187 				q0 = (value ? (*temp == '?' ? cls1 : cls2) : (*temp == '?' ? cls2 : cls1));
188 				if (q0) {
189 					t0 = part_property_expand_macros (part, q0);
190 					if (!t0) {
191 						g_warning ("error in template: %s", temp);
192 						g_free (qn);
193 					} else {
194 						out = g_string_append (out, t0);
195 						g_free (t0);
196 					}
197 				}
198 			} else if (*temp == '#') {
199 				if (value) {
200 					t0 = part_property_expand_macros (part, value);
201 					if (!t0) {
202 						g_warning ("error in template: %s", temp);
203 						g_free (qn);
204 					} else {
205 						out = g_string_append (out, t0);
206 						g_free (t0);
207 					}
208 				} else
209 					*(temp + sln) = 0;
210 			}
211 			temp += 1;
212 			temp += sln;
213 			g_free (qn);
214 			g_free (cls1);
215 			g_free (cls2);
216 		} else {
217 			if (*temp == '\\') {
218 				temp++;
219 				switch (*temp) {
220 				case 'n':
221 					out = g_string_append_c (out, '\n');
222 					break;
223 				case 't':
224 					out = g_string_append_c (out, '\t');
225 					break;
226 				case 'r':
227 					out = g_string_append_c (out, '\r');
228 					break;
229 				case 'f':
230 					out = g_string_append_c (out, '\f');
231 				}
232 				temp++;
233 			} else {
234 				out = g_string_append_c (out, *temp);
235 				temp++;
236 			}
237 		}
238 	}
239 
240 	g_free (tmp0);
241 
242 	out = g_string_append_c (out, '\0');
243 	ret = g_strdup (out->str);
244 	g_string_free (out, TRUE);
245 
246 	return ret;
247 }
248 
249 /**
250  * see #168
251  */
update_connection_designators(Part * part,char ** prop,int * node_ctr)252 void update_connection_designators(Part *part, char **prop, int *node_ctr)
253 {
254 	if (prop == NULL || *prop == NULL)
255 		return;
256 	if (node_ctr == NULL)
257 		return;
258 	if (part == NULL || !IS_PART(part))
259 		return;
260 
261 	char *temp = *prop;
262 	GString *out = g_string_new ("");
263 
264 	int breakout = FALSE;
265 	while (!breakout) {
266 		char **prop_split = g_regex_split_simple("[@?~#&%].*", temp, 0, 0);
267 		if (prop_split[0] == NULL) {
268 			g_strfreev(prop_split);
269 			break;
270 		}
271 		temp += strlen(prop_split[0]);
272 		g_string_append_printf(out, "%s", prop_split[0]);
273 		g_strfreev(prop_split);
274 		char macro = *temp;
275 		temp++;
276 		switch (macro) {
277 			case '%':
278 			{
279 				char **prop_split = g_regex_split_simple(" .*", temp, 0, 0);
280 				temp += strlen(prop_split[0]);
281 				g_string_append_printf(out, "%%%d", (*node_ctr)++);
282 				g_strfreev(prop_split);
283 				break;
284 			}
285 			case '@':
286 			{
287 				char **prop_split = g_regex_split_simple("[,.;/|() ].*", temp, 0, 0);
288 				temp += strlen(prop_split[0]);
289 				char *prop_ref_name = prop_split[0];
290 				char **prop_ref_value = part_get_property_ref(part, prop_ref_name);
291 				g_string_append_printf(out, "@%s", prop_ref_name);
292 				update_connection_designators(part, prop_ref_value, node_ctr);
293 				g_strfreev(prop_split);
294 				break;
295 			}
296 			case '&':
297 			{
298 				char **prop_split = g_regex_split_simple("[,.;/|() ].*", temp, 0, 0);
299 				temp += strlen(prop_split[0]);
300 				char *prop_ref_name = prop_split[0];
301 				char **prop_ref_value = part_get_property_ref(part, prop_ref_name);
302 				g_string_append_printf(out, "&%s", prop_ref_name);
303 				if (prop_ref_value != NULL && *prop_ref_value != NULL)
304 					update_connection_designators(part, prop_ref_value, node_ctr);
305 				g_strfreev(prop_split);
306 				break;
307 			}
308 			case '?':
309 			case '~':
310 			{
311 				char **prop_split = g_regex_split_simple("([,.;/|()])(.*?)(\\g{-3})(?(?=[,.;/|()])([,.;/|()])(.*?)(\\g{-3})).*", temp, 0, 0);
312 				char *prop_ref_name = g_strdup(prop_split[0]);
313 				char separator1 = *prop_split[1];
314 				char *cls1 = g_strdup(prop_split[2]);
315 				//separator1 == *prop_split_name[3]
316 				char separator2 = prop_split[4] != NULL ? *prop_split[4] : 0;
317 				char *cls2 = NULL;
318 				if (separator2 != 0) {
319 					cls2 = g_strdup(prop_split[5]);
320 				}
321 				char **prop_ref_value = part_get_property_ref(part, prop_ref_name);
322 				for (int i = 0; prop_split[i] != NULL; i++)
323 					temp += strlen(prop_split[i]);
324 				g_strfreev(prop_split);
325 
326 				if	(
327 						(macro == '?' && prop_ref_value != NULL && *prop_ref_value != NULL)
328 						||
329 						(macro == '~' && (prop_ref_value == NULL || *prop_ref_value == NULL))
330 					)
331 					update_connection_designators(part, &cls1, node_ctr);
332 				else if (cls2 != NULL)
333 					update_connection_designators(part, &cls2, node_ctr);
334 
335 				g_string_append_printf(out, "%c%s%c%s%c", macro, prop_ref_name, separator1, cls1, separator1);
336 				if (cls2 != NULL) {
337 					g_string_append_printf(out, "%c%s%c", separator2, cls2, separator2);
338 					g_free(cls2);
339 				}
340 
341 				g_free(cls1);
342 				g_free(prop_ref_name);
343 				break;
344 			}
345 			case '#':
346 			{
347 				char **prop_split = g_regex_split_simple("([,.;/|()])(.*?)(\\g{-3}).*", temp, 0, 0);
348 				char *prop_ref_name = g_strdup(prop_split[0]);
349 				char separator = *prop_split[1];
350 				char *cls = g_strdup(prop_split[2]);
351 				//separator == *prop_split_name[3]
352 				char **prop_ref_value = part_get_property_ref(part, prop_ref_name);
353 				for (int i = 0; prop_split[i] != NULL; i++)
354 					temp += strlen(prop_split[i]);
355 				g_strfreev(prop_split);
356 
357 				if (prop_ref_value != NULL && *prop_ref_value != NULL) {
358 					update_connection_designators(part, &cls, node_ctr);
359 					g_string_append_printf(out, "#%s%c%s%c", prop_ref_name, separator, cls, separator);
360 				} else {
361 					g_string_append_printf(out, "#%s%c%s%c%s", prop_ref_name, separator, cls, separator, temp);
362 					breakout = TRUE;
363 				}
364 
365 				g_free(cls);
366 				g_free(prop_ref_name);
367 				break;
368 			}
369 			default:
370 			{
371 				breakout = TRUE;
372 				break;
373 			}
374 		}
375 	}
376 	g_free(*prop);
377 	*prop = out->str;
378 	g_string_free (out, FALSE);
379 	return;
380 }
381