1/*
2 * "xmlppd.c"
3 *
4 * PPD to XML converter.
5 *
6 * Copyright 2007 by Michael R Sweet and Robert Krawitz
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 * 02110-1301 USA
22 */
23
24#include <stdio.h>
25#include <gutenprint/mxml.h>
26#include <gutenprint/util.h>
27#include <gutenprint/string-list.h>
28#include <stdlib.h>
29#include "xmlppd.h"
30
31typedef struct
32{
33  int order;
34  const char *name;
35} order_t;
36
37static int
38order_compare(const void *a, const void *b)
39{
40  const order_t *aa = (const order_t *)a;
41  const order_t *bb = (const order_t *)b;
42  if (aa->order < bb->order)
43    return -1;
44  else if (aa->order > bb->order)
45    return 1;
46  else
47    return strcmp(aa->name, bb->name);
48}
49
50
51static stp_mxml_node_t *
52find_element_named(stp_mxml_node_t *root, const char *name, const char *what)
53{
54  stp_mxml_node_t *element;
55  if (root && name)
56    {
57      for (element = stp_mxmlFindElement(root, root, what, NULL, NULL,
58					 STP_MXML_DESCEND);
59	   element;
60	   element = stp_mxmlFindElement(element, root, what, NULL, NULL,
61					 STP_MXML_DESCEND))
62	{
63	  if (!strcmp(stp_mxmlElementGetAttr(element, "name"), name))
64	    return element;
65	}
66    }
67  return NULL;
68}
69
70static stp_mxml_node_t *
71find_element_index(stp_mxml_node_t *root, int idx, const char *what)
72{
73  stp_mxml_node_t *element;
74  int count = 0;
75  if (root && idx >= 0)
76    {
77      for (element = stp_mxmlFindElement(root, root, what, NULL, NULL,
78					 STP_MXML_DESCEND);
79	   element;
80	   element = stp_mxmlFindElement(element, root, what, NULL, NULL,
81					 STP_MXML_DESCEND))
82	{
83	  if (count++ == idx)
84	    return element;
85	}
86    }
87  return NULL;
88}
89
90static size_t
91find_element_count(stp_mxml_node_t *root, const char *what)
92{
93  stp_mxml_node_t *element;
94  size_t count = 0;
95  if (root)
96    {
97      for (element = stp_mxmlFindElement(root, root, what, NULL, NULL,
98					 STP_MXML_DESCEND);
99	   element;
100	   element = stp_mxmlFindElement(element, root, what, NULL, NULL,
101					 STP_MXML_DESCEND))
102	count++;
103    }
104  return count;
105}
106
107stp_mxml_node_t *
108stpi_xmlppd_find_group_named(stp_mxml_node_t *root, const char *name)
109{
110  return find_element_named(root, name, "group");
111}
112
113stp_mxml_node_t *
114stpi_xmlppd_find_group_index(stp_mxml_node_t *root, int idx)
115{
116  return find_element_index(root, idx, "group");
117}
118
119int
120stpi_xmlppd_find_group_count(stp_mxml_node_t *root)
121{
122  return find_element_count(root, "group");
123}
124
125stp_mxml_node_t *
126stpi_xmlppd_find_option_named(stp_mxml_node_t *root, const char *name)
127{
128  return find_element_named(root, name, "option");
129}
130
131stp_mxml_node_t *
132stpi_xmlppd_find_option_index(stp_mxml_node_t *root, int idx)
133{
134  return find_element_index(root, idx, "option");
135}
136
137int
138stpi_xmlppd_find_option_count(stp_mxml_node_t *root)
139{
140  return find_element_count(root, "option");
141}
142
143stp_mxml_node_t *
144stpi_xmlppd_find_choice_named(stp_mxml_node_t *option, const char *name)
145{
146  return find_element_named(option, name, "choice");
147}
148
149stp_mxml_node_t *
150stpi_xmlppd_find_choice_index(stp_mxml_node_t *option, int idx)
151{
152  return find_element_index(option, idx, "choice");
153}
154
155int
156stpi_xmlppd_find_choice_count(stp_mxml_node_t *option)
157{
158  return find_element_count(option, "choice");
159}
160
161stp_mxml_node_t *
162stpi_xmlppd_find_page_size(stp_mxml_node_t *root, const char *name)
163{
164  return stpi_xmlppd_find_choice_named(stpi_xmlppd_find_option_named(root, "PageSize"), name);
165}
166
167static void
168parse_values(const char **data, int limit, char *value)
169{
170  int dptr = 0;
171  char *where = value;
172  char *end = value;
173  for (dptr = 0; dptr < limit; dptr++)
174    data[dptr] = NULL;
175  for (dptr = 0; *where && dptr < limit; dptr++)
176    {
177      where = end;
178      while (*where && isspace(*where))
179	where++;
180      end = where;
181      while (*end && !isspace(*end))
182	end++;
183      *end++ = '\0';
184      data[dptr] = where;
185    }
186}
187
188/*
189 * 'read_ppd_file()' - Read a PPD file into XML data.
190 */
191
192stp_mxml_node_t *				/* O - PPD file as XML */
193stpi_xmlppd_read_ppd_file(const char *filename)	/* I - PPD file */
194{
195  stp_mxml_node_t *ppd,			/* Root node of "ppd" group */
196		*group,			/* Current group */
197		*option,		/* Current option */
198		*choice;		/* Current choice */
199  FILE		*fp;			/* PPD file */
200  int		ch,			/* Current character */
201		sawcolon,		/* Saw a colon? */
202    		inquote,		/* In a quoted string? */
203    		num_choices = 0;
204  char		buffer[32768],		/* Line buffer */
205		*bufptr,		/* Pointer into line */
206		*xptr,		/* Pointer into line */
207		option_name[42],	/* Option name */
208		stp_option_data_name[64],	/* Option name */
209		*keyword,		/* Pointer to option keyword */
210		*text,			/* Pointer to text */
211		*value;			/* Pointer to value */
212  order_t	*order_array;		/* Precedence order of options */
213  int		i;
214  int		option_count;
215  int		order_length;
216  char		*order_list;
217  int		in_comment;
218  stp_string_list_t *ialist = stp_string_list_create();
219  stp_string_list_t *pdlist = stp_string_list_create();
220
221
222 /*
223  * Open the file...
224  */
225
226  if ((fp = fopen(filename, "rb")) == NULL)
227    {
228      perror(filename);
229      return (NULL);
230    }
231
232 /*
233  * Create the base PPD file tree; the completed tree will look like:
234  *
235  * <?xml version="1.0"?>
236  * <ppd color="0/1" level="1/2/3">
237  *     <option name="..." text="..." default="..." section="..." order="..."
238  *             type="...">
239  *         <choice name="..." text="...">code</choice>
240  *         ...
241  *     </option>
242  *     ...
243  *     <group name="..." text="...">
244  *         <option ...>
245  *              <choice ...>code</choice>
246  *         </option>
247  *     </group>
248  * </ppd>
249  */
250
251/*  xml = stp_mxmlNewXML("1.0"); */
252/*  ppd = stp_mxmlNewElement(xml, "ppd"); */
253  ppd = stp_mxmlNewElement(STP_MXML_NO_PARENT, "ppd");
254
255  stp_mxmlElementSetAttr(ppd, "color", "0");
256  stp_mxmlElementSetAttr(ppd, "level", "2");
257
258 /*
259  * Read all of the lines of the form:
260  *
261  *   *% comment
262  *   *Keyword: value
263  *   *Keyword OptionKeyword: value
264  *   *Keyword OptionKeyword/Text: value
265  *
266  * Only save groups, options, and choices, along with specific metadata
267  * from the file...
268  */
269
270  group          = NULL;
271  option         = NULL;
272  option_name[0] = '\0';
273
274  while ((ch = getc(fp)) != EOF)
275    {
276      /*
277       * Read the line...
278       */
279
280      buffer[0] = ch;
281      bufptr    = buffer + 1;
282      inquote   = 0;
283      sawcolon  = 0;
284      in_comment = 0;
285      if (ch == '*')
286	{
287	  if ((ch = getc(fp)) == '%')
288	    {
289	      while (1)
290		{
291		  ch = getc(fp);
292		  if (ch == '\n' || ch == EOF)
293		    break;
294		}
295	      continue;
296	    }
297	  else
298	    ungetc(ch, fp);
299	}
300
301      while ((ch != '\n' || inquote) && bufptr < (buffer + sizeof(buffer) - 1))
302	{
303	  if ((ch = getc(fp)) == '\r')
304	    {
305	      if ((ch = getc(fp)) != '\n')
306		ungetc(ch, fp);
307	    }
308
309	  *bufptr++ = ch;
310
311	  if (ch == ':' && !sawcolon)
312	    sawcolon = 1;
313	  else if (ch == '\"' && sawcolon)
314	    inquote = !inquote;
315	}
316
317      /*
318       * Strip trailing whitespace...
319       */
320
321      for (bufptr --; bufptr > buffer && isspace(bufptr[-1] & 255); bufptr --);
322
323      *bufptr = '\0';
324
325      /*
326       * Now parse it...
327       */
328
329      if (!strncmp(buffer, "*%", 2) || !buffer[0])
330	continue;
331
332      if ((value = strchr(buffer, ':')) == NULL)
333	continue;
334
335      for (*value++ = '\0';
336	   *value && (isspace(*value & 255) || (*value & 255) == '"');
337	   value ++);
338      for (xptr = value;
339	   *xptr && (*xptr & 255) != '"';
340	   xptr ++);
341      if (*xptr == '"')
342	*xptr = '\0';
343
344      for (keyword = buffer; *keyword && !isspace(*keyword & 255); keyword ++);
345
346      while (isspace(*keyword & 255))
347	*keyword++ = '\0';
348
349      if ((text = strchr(keyword, '/')) != NULL)
350	*text++ = '\0';
351
352      /*
353       * And then use the parsed values...
354       */
355
356      if (!strcmp(buffer, "*ColorDevice"))
357	{
358	  /*
359	   * Color support...
360	   */
361
362	  if (!strcasecmp(value, "true"))
363	    stp_mxmlElementSetAttr(ppd, "color", "1");
364	  else
365	    stp_mxmlElementSetAttr(ppd, "color", "0");
366	}
367      else if (!strcmp(buffer, "*LanguageLevel") && atoi(value) > 0)
368	{
369	  /*
370	   * PostScript language level...
371	   */
372
373	  stp_mxmlElementSetAttr(ppd, "level", value);
374	}
375      else if (!strcmp(buffer, "*StpDriverName"))
376	stp_mxmlElementSetAttr(ppd, "driver", value);
377      else if (!strcmp(buffer, "*ModelName"))
378	stp_mxmlElementSetAttr(ppd, "modelname", value);
379      else if (!strcmp(buffer, "*ShortNickName"))
380	stp_mxmlElementSetAttr(ppd, "shortnickname", value);
381      else if (!strcmp(buffer, "*NickName"))
382	stp_mxmlElementSetAttr(ppd, "nickname", value);
383      else if (!strcmp(buffer, "*OpenGroup"))
384	{
385	  if ((text = strchr(value, '/')) != NULL)
386	    *text++ = '\0';
387
388	  group = stp_mxmlNewElement(ppd, "group");
389	  stp_mxmlElementSetAttr(group, "name", value);
390	  stp_mxmlElementSetAttr(group, "text", text ? text : value);
391	}
392      else if (!strcmp(buffer, "*CloseGroup"))
393	group = NULL;
394      else if ((!strcmp(buffer, "*OpenUI") || !strcmp(buffer, "*JCLOpenUI")) &&
395	       keyword[0] == '*' && keyword[1])
396	{
397	  /*
398	   * Start a new option...
399	   */
400
401	  option = stp_mxmlNewElement(group ? group : ppd, "option");
402	  stp_mxmlElementSetAttr(option, "name", keyword + 1);
403	  stp_mxmlElementSetAttr(option, "text", text ? text : keyword + 1);
404	  stp_mxmlElementSetAttr(option, "ui", value);
405
406	  strncpy(option_name, keyword, sizeof(option_name) - 1);
407	  option_name[sizeof(option_name) - 1] = '\0';
408	  strcpy(stp_option_data_name, "*Stp");
409	  strcpy(stp_option_data_name + 4, option_name + 1);
410	  if (group)
411	    {
412	      stp_mxmlElementSetAttr(option, "groupname", stp_mxmlElementGetAttr(group, "name"));
413	      stp_mxmlElementSetAttr(option, "grouptext", stp_mxmlElementGetAttr(group, "text"));
414	    }
415	  num_choices = 0;
416	}
417      else if (option && !strcmp(buffer, stp_option_data_name))
418	{
419	  const char *data[8];
420	  parse_values(data, 8, value);
421	  if (data[7])
422	    {
423	      stp_mxmlElementSetAttr(option, "stptype", data[0]);
424	      stp_mxmlElementSetAttr(option, "stpmandatory", data[1]);
425	      stp_mxmlElementSetAttr(option, "stpclass", data[2]);
426	      stp_mxmlElementSetAttr(option, "stplevel", data[3]);
427	      stp_mxmlElementSetAttr(option, "stpchannel", data[4]);
428	      stp_mxmlElementSetAttr(option, "stplower", data[5]);
429	      stp_mxmlElementSetAttr(option, "stpupper", data[6]);
430	      stp_mxmlElementSetAttr(option, "stpdefault", data[7]);
431	      stp_mxmlElementSetAttr(option, "stpname", stp_option_data_name + 7);
432	    }
433	}
434      else if (!strcmp(buffer, "*OrderDependency") && option)
435	{
436	  /*
437	   * Get order and section for option
438	   */
439
440	  char	order[256],		/* Order number */
441	    section[256];		/* Section name */
442
443
444	  if (sscanf(value, "%255s%255s", order, section) == 2)
445	    {
446	      stp_mxmlElementSetAttr(option, "order", order);
447	      stp_mxmlElementSetAttr(option, "section", section);
448	    }
449	}
450      else if (!strncmp(buffer, "*Default", 8) && option &&
451	       !strcmp(buffer + 8, option_name + 1))
452	stp_mxmlElementSetAttr(option, "default", value);
453      else if (!strcmp(buffer, "*CloseUI") || !strcmp(buffer, "*JCLCloseUI"))
454	{
455	  char buf[64];
456	  (void) sprintf(buf, "%d", num_choices);
457	  stp_mxmlElementSetAttr(option, "num_choices", buf);
458	  option = NULL;
459	  stp_option_data_name[0] = '\0';
460	}
461      else if (option && !strcmp(buffer, option_name))
462	{
463	  /*
464	   * A choice...
465	   */
466
467	  choice = stp_mxmlNewElement(option, "choice");
468	  stp_mxmlElementSetAttr(choice, "name", keyword);
469	  stp_mxmlElementSetAttr(choice, "text", text ? text : keyword);
470
471	  if (value[0] == '\"')
472	    value ++;
473
474	  if (bufptr > buffer && bufptr[-1] == '\"')
475	    {
476	      bufptr --;
477	      *bufptr = '\0';
478	    }
479
480	  stp_mxmlNewOpaque(choice, value);
481	  num_choices++;
482	}
483      else if (!option && !strcmp(buffer, "*ImageableArea"))
484	{
485	  stp_string_list_add_string(ialist, keyword, value);
486	}
487      else if (!option && !strcmp(buffer, "*PaperDimension"))
488	{
489	  stp_string_list_add_string(pdlist, keyword, value);
490	}
491    }
492  if (option)
493    {
494      char buf[64];
495      (void) sprintf(buf, "%d", num_choices);
496      stp_mxmlElementSetAttr(option, "num_choices", buf);
497      option = NULL;
498      stp_option_data_name[0] = '\0';
499    }
500
501  for (i = 0; i < stp_string_list_count(ialist); i++)
502    {
503      stp_param_string_t *pstr = stp_string_list_param(ialist, i);
504      stp_mxml_node_t *psize = stpi_xmlppd_find_page_size(ppd, pstr->name);
505      if (psize)
506	{
507	  const char *data[4];
508	  value = stp_strdup(pstr->text);
509	  parse_values(data, 4, value);
510	  if (data[3])
511	    {
512	      stp_mxmlElementSetAttr(psize, "left", data[0]);
513	      stp_mxmlElementSetAttr(psize, "bottom", data[1]);
514	      stp_mxmlElementSetAttr(psize, "right", data[2]);
515	      stp_mxmlElementSetAttr(psize, "top", data[3]);
516	    }
517	  stp_free(value);
518	}
519    }
520  stp_string_list_destroy(ialist);
521  for (i = 0; i < stp_string_list_count(pdlist); i++)
522    {
523      stp_param_string_t *pstr = stp_string_list_param(pdlist, i);
524      stp_mxml_node_t *psize = stpi_xmlppd_find_page_size(ppd, pstr->name);
525      if (psize)
526	{
527	  const char *data[2];
528	  value = stp_strdup(pstr->text);
529	  parse_values(data, 2, value);
530	  if (data[1])
531	    {
532	      stp_mxmlElementSetAttr(psize, "width", data[0]);
533	      stp_mxmlElementSetAttr(psize, "height", data[1]);
534	    }
535	  stp_free(value);
536	}
537    }
538  stp_string_list_destroy(pdlist);
539  option_count = stpi_xmlppd_find_option_count(ppd);
540  order_length = 1;		/* Terminating null */
541  order_array = malloc(sizeof(order_t) * option_count);
542  i = 0;
543  for (option = stp_mxmlFindElement(ppd, ppd, "option", NULL, NULL,
544				    STP_MXML_DESCEND);
545       option && i < option_count;
546       option = stp_mxmlFindElement(option, ppd, "option", NULL, NULL,
547				    STP_MXML_DESCEND))
548    {
549      if (stp_mxmlElementGetAttr(option, "order"))
550	{
551	  order_array[i].name = stp_mxmlElementGetAttr(option, "name");
552	  order_length += strlen(order_array[i].name) + 1;
553	  order_array[i].order = atoi(stp_mxmlElementGetAttr(option, "order"));
554	  i++;
555	}
556    }
557  option_count = i;
558  qsort(order_array, option_count, sizeof(order_t), &order_compare);
559  order_list = malloc(order_length);
560  order_length = 0;
561  for (i = 0; i < option_count; i++)
562    {
563      if (i > 0)
564	order_list[order_length++] = ' ';
565      strcpy(order_list + order_length, order_array[i].name);
566      order_length += strlen(order_array[i].name);
567    }
568  stp_mxmlElementSetAttr(ppd, "optionorder", order_list);
569  free(order_list);
570  free(order_array);
571  return (ppd);
572}
573
574/*
575 * End of "xmlppd.c".
576 */
577