added Info.plist
[TestXSLT.git] / libsablot / src / command / sabcmd.cpp
1 /* 
2  * The contents of this file are subject to the Mozilla Public
3  * License Version 1.1 (the "License"); you may not use this file
4  * except in compliance with the License. You may obtain a copy of
5  * the License at http://www.mozilla.org/MPL/
6  * 
7  * Software distributed under the License is distributed on an "AS
8  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
9  * implied. See the License for the specific language governing
10  * rights and limitations under the License.
11  * 
12  * The Original Code is the Sablotron XSLT Processor.
13  * 
14  * The Initial Developer of the Original Code is Ginger Alliance Ltd.
15  * Portions created by Ginger Alliance are Copyright (C) 2000-2002
16  * Ginger Alliance Ltd. All Rights Reserved.
17  * 
18  * Contributor(s): Christian Lefebvre, Stefan Behnel
19  * 
20  * Alternatively, the contents of this file may be used under the
21  * terms of the GNU General Public License Version 2 or later (the
22  * "GPL"), in which case the provisions of the GPL are applicable 
23  * instead of those above.  If you wish to allow use of your 
24  * version of this file only under the terms of the GPL and not to
25  * allow others to use your version of this file under the MPL,
26  * indicate your decision by deleting the provisions above and
27  * replace them with the notice and other provisions required by
28  * the GPL.  If you do not delete the provisions above, a recipient
29  * may use your version of this file under either the MPL or the
30  * GPL.
31  */
32
33 /*****************************************************************
34
35   s a b c m d
36
37 *****************************************************************/
38
39 /*****************************************************************
40 defines
41 *****************************************************************/
42
43 #define BARE_SIGN '@'
44
45 #define MAX_BARE        64
46 #define MAX_EQ          16
47 #define MAX_ARG_OR_PAR  MAX_EQ
48
49 /*****************************************************************
50 includes
51 *****************************************************************/
52
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #include <sablot.h>
57
58 // include for time measurement
59 #include <time.h>      // needed by <sys/timeb.h> for definition of time_t
60
61 #if defined(HAVE_CONFIG_H)
62 #include <config.h>
63 #endif
64
65 #ifdef SABLOT_DEBUGGER
66 #include <sabdbg.h>
67 #endif
68
69 #if defined(HAVE_SYS_TIMEB_H) || defined(WIN32)
70 #include <sys/timeb.h>
71 #endif
72
73 #ifdef HAVE_SYS_TYPES_H
74 #include <sys/types.h> // needed too in Windows
75 #endif
76
77 #ifdef HAVE_SYS_TIME_H
78 #include <sys/time.h>
79 #endif
80
81 /*****************************************************************
82 vars to hold the results of switch parsing
83 *****************************************************************/
84
85 int batchMode = 0,
86   numberTimes = 0,
87   numberMode = 0,
88   numberMeasure = 0,
89   commandFlags = 0;
90
91 char * stringBase = NULL,
92     * stringLog = NULL;
93
94 /*****************************************************************
95 switch definitions
96 *****************************************************************/
97
98 typedef enum 
99 {
100     ID_NONE,
101     ID_SPS, ID_SPF, ID_SPS_ON_FILES, ID_BASE, ID_TIMES,
102     ID_LOG, ID_HELP, ID_DEBUG, ID_DEBUGGER, ID_DBG_HELP, ID_FLAGS, 
103     ID_VERSION, ID_MEASURE, ID_BATCH_XML, ID_BATCH_XSL,
104     ID_CHAIN_XSL
105 } SwitchId;
106
107 struct SwitchData
108 {
109     SwitchId id;
110     char *longName, shortName;
111     char type;  // Integer, String, Ordinal
112     void *destination;
113 };
114
115 struct SwitchData switches[] =
116 {
117     {ID_SPS, "use-SPS", 'S', 'O', &numberMode},   
118     {ID_SPF, "use-SPF", 'F', 'O', &numberMode},   
119     {ID_SPS_ON_FILES, "use-SPS-on-files", 0, 'O', &numberMode},
120     {ID_BASE, "base", 'b', 'S', &stringBase},            
121     {ID_TIMES, "times", 't', 'N', &numberTimes},          
122     {ID_LOG, "log-file", 'L', 'S', &stringLog},         
123     {ID_HELP, "help", '?', 'O', &numberMode},            
124     {ID_DEBUG, "debug", 0, 'O', &numberMode},
125     {ID_DEBUGGER, "debugger", 0, 'O', &numberMode},
126     {ID_DBG_HELP, "debug-options", 0, 'O', &numberMode},
127     {ID_FLAGS, "flags", 'f', 'N', &commandFlags},
128     {ID_VERSION, "version", 'v', 'O', &numberMode},
129     {ID_MEASURE, "measure", 'm', 'O', &numberMeasure},
130     {ID_BATCH_XML, "batch-xml", 'x', 'O', &batchMode},
131     {ID_BATCH_XSL, "batch-xsl", 's', 'O', &batchMode},
132     {ID_CHAIN_XSL, "chain-xsl", 'c', 'O', &batchMode},
133     {ID_NONE, NULL, 0, 0, NULL }
134 };
135
136 /*****************************************************************
137 texts
138 *****************************************************************/
139
140 char usage[] = 
141 "\nUsage for single XSLT:\n"\
142 "\tsabcmd [options] <stylesheet> [<input> [<output>]] [assignments]\n"\
143 "\nUsage in batch processing:\n"\
144 "    chain multiple stylesheets, starting with a single input file:\n"\
145 "    \tsabcmd [options] --chain-xsl <input> <output> [<stylesheet>]+ [assignments]\n"\
146 "    run multiple stylesheets on a single input file:\n"\
147 "    \tsabcmd [options] --batch-xml <input> [<stylesheet> <output>]+ [assignments]\n"\
148 "    run a single stylesheet on multiple input files:\n"\
149 "    \tsabcmd [options] --batch-xsl <stylesheet> [<input> <output>]+ [assignments]\n"\
150 "Options:\n"\
151 "\t--chain-xsl, -c      single input file, multiple chained stylesheets\n"\
152 "\t--batch-xml, -x      single input file, multiple stylesheets\n"\
153 "\t--batch-xsl, -s      multiple input files, single stylesheet\n"\
154 "\t--base=NAME, -b      set the hard base URI to NAME\n"\
155 "\t--debug-options      display information on debugging options\n"\
156 "\t--help, -?           display this help message\n"\
157 "\t--log-file=NAME, -L  set the log file, turn logging on\n"\
158 "\t--measure, -m        measure the processing time\n"\
159 "\t--version, -v        display version information\n"\
160 "Defaults:\n\t<input> = stdin, <output> = stdout\n"\
161 "\tlogging off, no hard base URI\n"\
162 "Notes:\n"\
163 "\t- assignments define named buffers (name=value)\n"\
164 "\t  and top-level params ($name=value).\n"\
165 "\t- to specify value in an option, use -b=NAME or -b NAME\n"\
166 "\t  (correspondingly for the equivalent long options)";
167
168 char askhelp[] = "Type sabcmd --help to display a help message.\n";
169 char msgConflict[] = "conflict with preceding switches: ";
170 char version_txt[] = "\nsabcmd "SAB_VERSION" ("SAB_DATE")\n"\
171     "copyright (C) 2000 - 2002 Ginger Alliance (www.gingerall.com)\n";
172 char dbg_usage[] = "\nDebugging options:\n"\
173     "\t--debug\t\t\tdisplay results of the command line parse\n"\
174     "\t--debugger\t\trun the xslt debugger\n"\
175     "\t--times=COUNT, -t\trun sabcmd the specified number of times\n"\
176     "\t--flags=FLAGS, -f\tset processor flags for the processing\n"\
177     "\t--use-SPF, -F\t\tuse SablotProcessFiles().\n"\
178     "\t--use-SPS, -S\t\tuse SablotProcessStrings(). Give 2 args\n"\
179     "\t\t\t\t(stylesheet, input). Precede each by @.\n" \
180     "\t--use-SPS-on-files\tuse SablotProcessStrings() on the contents\n"\
181     "\t\t\t\tof the given files.";
182 /* removed the GPL notice: 
183 char startup_licence[]="sabcmd and Sablotron come with ABSOLUTELY NO WARRANTY. "\
184     "They are free software,\n"\
185     "and you are welcome to redistribute them under certain conditions.\n"\
186     "For details on warranty and the redistribution terms, see the README file.\n";
187 */
188 char startup_licence[] = "The Sablotron XSLT Processor comes with NO WARRANTY.\n"\
189     "It is subject to the Mozilla Public License Version 1.1.\n"\
190     "Alternatively, you may use Sablotron under the GNU General Public License.\n";
191
192 /*****************************************************************
193 handy functions
194 *****************************************************************/
195
196 void saberr(char *msg1, char *msg2)
197 {
198     fprintf(stderr, "Error: %s", msg1);
199     if (msg2) fputs(msg2, stderr);
200     fprintf(stderr, "\n%s", askhelp);
201     exit(EXIT_FAILURE);
202 }
203
204 void saberrn(char *msg, int num)
205 {
206     char buf[10];
207     sprintf(buf,"%d",num);
208     saberr(msg, buf);
209 }
210
211 int chrpos(char *text, char c)
212 {
213     char *p = strchr(text, c);
214     return p ? (int)(p - text) : -1;
215 }
216
217 void freefirst(char **array)
218 {
219     for (; *array; array += 2)
220         free(*array);
221 }
222
223 void copyAssignment(char **array, int index, char *text, int split)
224 {
225     char *p;
226     if (index >= MAX_ARG_OR_PAR)
227       saberr("too many assignments", NULL);
228     array[2 * index + 1] = text + split + 1;
229     p = array[2 * index] = (char*) malloc(split + 1);
230     memcpy(p, text, split);
231     p[split] = 0;
232 }
233
234 /*****************************************************************
235     switch support
236 *****************************************************************/
237
238 // globals to hold the names and assignments
239
240 char* arrayBare[MAX_BARE];
241 char* arrayEq[MAX_EQ]; 
242 int indexBare = 0, indexEq = 0;
243
244 /*****************************************************************
245 findSwitch
246 *****************************************************************/
247
248 int max_strncmp(char *s1, char *s2, int len1)
249 {
250     int len2 = strlen(s2);
251     return strncmp(s1, s2, len1 >= len2 ? len1 : len2);
252 }
253
254 int findSwitch(char *text)
255 {
256     int islong = (text[1] == '-');
257     char *stripped = text + (islong ? 2 : 1);
258     int eqpos = chrpos(stripped, '=');
259     int index = 0;
260     while (switches[index].id != ID_NONE)
261     {
262         if ((eqpos >= 0 && islong && !max_strncmp(stripped, switches[index].longName, eqpos)) ||
263             (eqpos < 0 && islong && !strcmp(stripped, switches[index].longName)) ||
264             (stripped[0] == switches[index].shortName && ! islong && (eqpos == 1 || !stripped[1])))
265             return index + 1;
266         index++;
267     }
268     return 0;
269 }
270
271 /*****************************************************************
272 applySwitch
273 ord is the 1-based index of the switch!
274 *****************************************************************/
275
276 #define cassign(WHERE, WHAT, TEXT) \
277 { if (WHERE) saberr(msgConflict, (TEXT)); else WHERE = (WHAT); }
278
279
280 int applySwitch(int ord, char *text, char *following)
281 {
282     char *value = NULL;
283     char type = switches[--ord].type;       // make ord 0-based
284
285     int skip = 0;
286     int eqpos = chrpos(text, '=');
287     if (eqpos >= 0)
288         value = text + eqpos + 1;
289     else
290     {
291         if ((type == 'S' || type == 'N') && following && *following != '-')
292         {
293             value = following;
294             skip = 1;
295         }
296     }
297     switch (switches[ord].type)
298     {
299     case 'S': 
300         {
301             if (!value) saberr("switch needs a string value: ", text);
302             cassign(*(char**)(switches[ord].destination), value, text);
303         }; break;
304     case 'N':
305         {
306             char *stopper;
307             long number = 0;
308             if (value)
309                 number = strtol(value, &stopper, 0);
310             if (!value || *stopper)
311                 saberr("switch needs a number value: ", text);
312             cassign(*(int*)(switches[ord].destination), (int) number, text);
313         }; break;
314     case 'O':
315         {
316             if (value)
317                 saberr("value not recognized in switch: ", text);
318             cassign(*(int*)(switches[ord].destination), switches[ord].id, text);
319         }; break;
320     default:
321         saberr("error processing the switches", NULL);
322     }
323     return skip;
324 }
325
326 /*****************************************************************
327 readSwitches
328 *****************************************************************/
329
330 void readSwitches(int argc, char** argv)
331 {
332     int i, ord;
333     for (i = 1; i < argc; i++)
334     {
335         if (argv[i][0] == '-')
336         {
337             if (!(ord = findSwitch(argv[i])))
338                 saberr("invalid switch ", argv[i]);
339             else
340                 i += applySwitch(ord, argv[i], 
341                     (i < argc-1) ? argv[i+1] : NULL);
342                 continue;
343         }
344         else
345         {
346             if (((chrpos(argv[i], '=')) != -1) && (argv[i][0] != BARE_SIGN))
347             {
348                 if (indexEq >= MAX_EQ)
349                     saberrn("too many assignments, allowing ", MAX_EQ);
350                 else
351                     arrayEq[indexEq++] = argv[i];
352             }
353             else
354             {
355                 if (indexEq >= MAX_BARE)
356                     saberrn("too many names, allowing ", MAX_BARE);
357                 else
358                     arrayBare[indexBare++] = argv[i] + (argv[i][0] == BARE_SIGN);
359             }
360         }
361     }
362 };
363
364 /*****************************************************************
365 xformToPairs
366 *****************************************************************/
367
368 void xformToPairs(char** array, int count,
369     char** _params, int* _paramsC,
370     char** _args, int* _argsC)
371 {
372     int i, pos;
373     *_paramsC = *_argsC = 0;
374     for (i = 0 ; i < count; i++)
375     {
376         pos = chrpos(array[i], '=');
377         if (array[i][0] == '$')
378             copyAssignment(_params, (*_paramsC)++, array[i] + 1, pos-1);
379         else
380             copyAssignment(_args, (*_argsC)++, array[i], pos);
381     };
382     _args[2 * *_argsC] = NULL;
383     _params[2 * *_paramsC] = NULL;
384 }
385
386 /*****************************************************************
387 debug()
388 for debugging sabcmd itself
389 *****************************************************************/
390
391 #define safe(PTR) (PTR? PTR : "[null]")
392
393 void dumparray(char* caption, char** array, int count, int asPairs)
394 {
395     char **p = array;
396     printf("%s (%d)\n",caption, count);
397     while(*p)
398     {
399         printf("\t%s",*p);
400         if (asPairs)
401         {
402             printf(" = %s\n",safe(p[1]));
403             p += 2;
404         }
405         else 
406         {
407             puts("");
408             p++;
409         }
410     }
411 }
412
413 void debug(int cParams, char **params, int cArgs, char **args)
414 {
415     puts("\nCommand line parse results:");
416     dumparray("Names", arrayBare, indexBare, 0);
417     dumparray("Parameters", params, cParams, 1);
418     dumparray("Named buffers", args, cArgs, 1);   
419     printf("Settings \n\tTimes\t\t%d \n\tMode\t\t%d\n",
420         numberTimes, numberMode);
421     printf("\tBase\t\t%s \n\tLog\t\t%s\n", safe(stringBase), safe(stringLog));
422 }
423
424 double getExactTime()
425 {
426     double ret;
427 #if defined (WIN32)
428     struct _timeb theTime;
429     _ftime(&theTime);
430     ret = theTime.time + theTime.millitm/1000.0;
431 #elif defined (HAVE_FTIME)
432     struct timeb theTime;
433     ftime(&theTime);
434     ret = theTime.time + theTime.millitm/1000.0;
435 #elif defined (HAVE_GETTIMEOFDAY)
436     timeval theTime;
437     gettimeofday(&theTime, NULL);
438     ret = theTime.tv_sec + theTime.tv_usec/1000000.0;
439 #else
440 #error "Can't find function ftime() or similar"
441 #endif
442     return ret;
443 }
444
445
446 /*****************************************************************
447 main
448 *****************************************************************/
449
450 void full_version_txt()
451 {
452     puts(version_txt);
453     puts(startup_licence);
454 }
455
456
457 char* readToBuffer(char *filename)
458 {
459     long length;
460     int count;
461     char *buf;
462     FILE *f = fopen(filename, "rt");
463     if (!f)
464         saberr("cannot open file ", filename);
465     length = 0xffff;
466     buf = (char*) malloc(length + 1);
467     count = fread(buf, 1, length + 1, f);
468     fclose(f);
469     if (count > length)
470         saberr("file too large (64k limit)", NULL);
471     buf[count] = 0;
472     return buf;
473 }
474
475
476 int runSingleXSLT(const char **arrayParams, const char **arrayArgs, 
477                   char **resultArg)
478 {
479    void *theProcessor;
480    char *resultURI = NULL;
481    int ecode;
482    void *situa = NULL;
483    
484    if (ecode = SablotCreateSituation(&situa))
485      return ecode;
486
487    if (commandFlags)
488      SablotSetOptions(situa, commandFlags);
489
490    if (ecode = SablotCreateProcessorForSituation(situa, &theProcessor))
491       return ecode;
492
493    if (stringLog)
494       SablotSetLog(theProcessor, stringLog, 0);
495
496    if (stringBase)
497       SablotSetBase(theProcessor, stringBase);
498
499    if (indexBare <= 2)
500       resultURI = (char*)"file://stdout";
501    else
502       resultURI = arrayBare[2];
503
504    ecode = SablotRunProcessor(theProcessor,
505         arrayBare[0],
506         indexBare <= 1  ? (char*)"file://stdin" : arrayBare[1],
507         resultURI,
508         arrayParams, arrayArgs);
509
510    if (ecode == 0)
511       SablotGetResultArg(theProcessor, resultURI, resultArg);
512                 
513    if (theProcessor)
514       SablotDestroyProcessor(theProcessor);
515
516    if (situa)
517      SablotDestroySituation(situa);
518
519    return ecode;
520 }
521
522
523 int runBatchXSLT(const char **arrayParams, const char **arrayArgs, 
524                  char **resultArg)
525 {
526    void *theProcessor = NULL, *sabSit = NULL, *preparsed = NULL;
527    char *resultURI = (char*)"file://stdout";
528    char *input = NULL;
529    int ecode;
530    int argcount = 2, firstarg = 1;
531
532    if ((batchMode != ID_BATCH_XML) &&
533        (batchMode != ID_BATCH_XSL) &&
534        (batchMode != ID_CHAIN_XSL))
535       return runSingleXSLT(arrayParams, arrayArgs, resultArg);
536
537    ecode = SablotCreateSituation(&sabSit);
538    if (ecode != 0)
539       return ecode;
540
541    if (commandFlags)
542      SablotSetOptions(sabSit, commandFlags);
543
544    ecode = SablotCreateProcessorForSituation(sabSit, &theProcessor);
545
546    if (ecode == 0)
547    {
548       if (stringLog)
549          SablotSetLog(theProcessor, stringLog, 0);
550
551       if (stringBase)
552          SablotSetBase(theProcessor, stringBase);
553
554       switch (batchMode)
555       {
556          case ID_BATCH_XSL:
557             ecode = SablotParseStylesheet(sabSit, arrayBare[0], &preparsed);
558             break;
559          case ID_BATCH_XML:
560             ecode = SablotParse(sabSit, arrayBare[0], &preparsed);
561             break;
562          case ID_CHAIN_XSL:
563             input = arrayBare[0];
564             argcount = 1;
565             firstarg = 2;
566             break;
567       }
568    }
569
570    for (int t = firstarg; (t <= indexBare-argcount) && (ecode == 0); t += argcount)
571       {
572          if (arrayParams)
573          {
574             const char **params = arrayParams;
575             while ((params[0] != NULL) && (ecode == 0))
576             {
577                ecode = SablotAddParam(sabSit, theProcessor, params[0], params[1]);
578                params += 2;
579             }
580          }
581
582          if (arrayArgs)
583          {
584             const char **args = arrayArgs;
585             while ((args[0] != NULL) && (ecode == 0))
586             {
587                ecode = SablotAddArgBuffer(sabSit, theProcessor, args[0], args[1]);
588                args += 2;
589             }
590          }
591
592          if (ecode == 0)
593          {
594          switch (batchMode)
595             {
596             case ID_BATCH_XSL:
597             {
598                const char *document = arrayBare[t];
599                const char *output = arrayBare[t+1];
600                SablotAddArgTree(sabSit, theProcessor, "stylesheet", preparsed);
601                ecode = SablotRunProcessorGen(sabSit, theProcessor, "arg:stylesheet", document, output);
602             } break;
603             case ID_BATCH_XML:
604             {
605                const char *stylesheet = arrayBare[t];
606                const char *output = arrayBare[t+1];
607                SablotAddArgTree(sabSit, theProcessor, "source", preparsed);
608                ecode = SablotRunProcessorGen(sabSit, theProcessor, stylesheet, "arg:source", output);
609             } break;
610             case ID_CHAIN_XSL:
611             {
612                const char *stylesheet = arrayBare[t];
613                char *output;
614                if (t < indexBare-argcount)
615                   output = (char*)"arg:output";
616                else
617                   output = arrayBare[1];
618
619                ecode = SablotRunProcessorGen(sabSit, theProcessor, stylesheet, input, output);
620
621             if (ecode == 0)
622                {
623                   if (input == arrayBare[0])
624                      input = "arg:buffer";
625                   else
626                      SablotFree(*resultArg);
627
628                   SablotGetResultArg(theProcessor, output, resultArg);
629                   SablotAddArgBuffer(sabSit, theProcessor, "buffer", *resultArg);
630                }
631             } break;
632          }
633       }
634    }
635
636    if ((ecode == 0) && (batchMode != ID_CHAIN_XSL))
637       SablotGetResultArg(theProcessor, resultURI, resultArg);
638
639    if (preparsed)
640       SablotDestroyDocument(sabSit, preparsed);
641
642    if (theProcessor)
643       SablotDestroyProcessor(theProcessor);
644
645    if (sabSit)
646       SablotDestroySituation(sabSit);
647
648    return ecode;
649 }
650
651 void runDebugger()
652 {
653 #ifdef SABLOT_DEBUGGER
654   debuggerInit();
655   debuggerEnterIdle();
656   debuggerDone();
657 #else
658   printf("Debugegr is not supported in this build\n");
659 #endif
660 }
661
662 int main(int argc, char *argv[])
663 {
664     int indexParams, indexArgs, ecode, i;
665     char *arrayParams[2 * MAX_ARG_OR_PAR + 2],
666       *arrayArgs[2 * MAX_ARG_OR_PAR + 2];
667     char *resultArg = NULL;
668     double timeZero = 0;
669
670     // the following set the globals arrayBare and arrayEq
671     // we get the number of words/assignments in indexBare/indexEq
672
673     readSwitches(argc, argv);
674
675     switch(numberMode)
676     {
677     case ID_HELP:
678         {
679             puts(usage); return 0;
680         }; break;
681     case ID_DBG_HELP:
682         {
683             puts(dbg_usage); return 0;
684         }; break;
685     case ID_VERSION:
686         {
687             full_version_txt(); return 0;
688         }; break;
689     case ID_DEBUGGER:
690       {
691         runDebugger(); return 0;
692       }; break;
693     }
694
695     if (!indexBare && numberMode != ID_DEBUG)
696     {
697         full_version_txt();
698         saberr("stylesheet not given", NULL);
699     }
700
701     xformToPairs(arrayEq, indexEq, 
702         arrayParams, &indexParams,
703         arrayArgs, &indexArgs);
704
705     if (numberMode == ID_DEBUG)
706     {   
707         debug(indexParams, arrayParams, indexArgs, arrayArgs); 
708         return 0;
709     };
710
711     if (!numberTimes)
712         numberTimes = 1;
713     if (numberMeasure)
714         timeZero = getExactTime();
715
716     for (i = 0; i < numberTimes; i++)
717     {
718         switch(numberMode)
719         {
720         case 0: 
721           {
722              if ((batchMode == ID_BATCH_XML) || (batchMode == ID_BATCH_XSL) || (batchMode == ID_CHAIN_XSL))
723               ecode = runBatchXSLT((const char**)arrayParams, 
724                 (const char**)arrayArgs, &resultArg);
725             else
726               ecode = runSingleXSLT((const char**)arrayParams, 
727                                     (const char**)arrayArgs, &resultArg);
728           }; break;
729         case ID_SPS:
730             {
731                 if (indexBare != 2)
732                     saberr("SablotProcessStrings() needs exactly two arguments", NULL);
733                 ecode = SablotProcessStrings(arrayBare[0], arrayBare[1], &resultArg);
734             }; break;
735         case ID_SPS_ON_FILES:
736             {
737                 char *sheetBuf, *inputBuf;
738                 if (indexBare != 2)
739                     saberr("need exactly two arguments", NULL);
740                 sheetBuf = readToBuffer(arrayBare[0]);
741                 inputBuf = readToBuffer(arrayBare[1]);
742                 ecode = SablotProcessStrings(sheetBuf, inputBuf, &resultArg);
743                 free(sheetBuf);
744                 free(inputBuf);
745             }; break;
746         case ID_SPF: 
747             ecode = SablotProcessFiles(arrayBare[0],
748                 indexBare <= 1  ? (char*)"file://stdin" : arrayBare[1],
749                 indexBare <= 2  ? (char*)"file://stdout" : arrayBare[2]);
750             break;
751         }
752         
753         if (ecode)
754             return ecode;
755         
756         if (resultArg)
757         {
758             fprintf(stderr, "output buffer sent to stdout\n");
759             puts(resultArg);
760             SablotFree(resultArg);
761             resultArg = NULL;
762         }
763     };
764
765     if (numberMeasure)
766         fprintf(stderr, "Total time: %4.3f seconds\n", getExactTime() - timeZero);
767         
768     freefirst(arrayArgs);
769     freefirst(arrayParams);
770
771     return 0;
772 }
773