-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathjcl2xml.rex
2607 lines (2468 loc) · 98.1 KB
/
jcl2xml.rex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*REXX 2.0.0
Copyright (c) 2011-2020, Andrew J. Armstrong
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*REXX*****************************************************************
** **
** NAME - JCL2XML **
** **
** FUNCTION - Creates an XML and GraphML representation of JCL. **
** **
** This is the first step in creating stunning visual **
** documentation of your JCL. GraphML (Graph Markup **
** Language) is an XML standard for describing the nodes **
** and edges in a graph - in the mathematical sense. **
** In plain English it describes boxes connected by lines **
** in a diagram. **
** **
** The GraphML output can be viewed by a GraphML editor **
** such as yEd from yWorks (www.yworks.com) and exported **
** to SVG or PDF format for posting on your intranet. **
** **
** The XML output can be used for any purpose. For **
** example, you may transform it using XSLT, say, into **
** documentation. You could also use it to recreate your **
** JCL will all COND= steps replaced by IF/THEN/ELSE. **
** **
** USAGE - You can run this Rexx on an IBM mainframe or on a PC **
** with no code changes. **
** **
** If you run JCL2XML on your PC, you should use Regina **
** Rexx from: **
** **
** http://regina-rexx.sourceforge.net **
** **
** or ooRexx from: **
** **
** http://oorexx.sourceforge.net **
** **
** If you run JCL2XML on your mainframe, you should use **
** ftp to download the resulting files to your PC by: **
** **
** ftp yourmainframe **
** youruserid **
** yourpassword **
** quote site sbdataconn=(IBM-1047,ISO8859-1) **
** get 'your.xml' your.xml **
** get 'your.gml' your.graphml **
** **
** Alternatively, you can download your JCL to a PC and **
** run JCL2XML on your PC by: **
** **
** rexx jcl2xml.rexx your.jcl **
** **
** This will read your.jcl and create your.xml and **
** your.graphml. **
** **
** With the GraphML output on your PC, you can use a **
** GraphML editor such as yEd from www.yworks.com to **
** view a flowchart-style representation of the JCL. **
** To do this with yEd: **
** **
** 1. Start yEd by running yed.exe **
** 2. Open the graphml file created by jcl2xml. You will **
** see crap piled on crap at this stage because the **
** graph has not been layed out yet... **
** 3. Choose Layout from the main menu **
** 4. Choose Orthogonal from the Layout dropdown **
** 5. Choose UML Style from the Orthogonal dropdown **
** 6. Click the Apply button. The result is a reasonable **
** attempt to layout the graph. You can tweak the **
** layout by clicking and dragging boxes and lines. **
** Displaying and snapping to grid lines is helpful **
** while doing the tweaking. **
** 7. If you intend to do this layout often, then click **
** the Dock button to move this dialog box to the **
** left hand side of the window. You will now be able **
** to just click the 'play' button to layout the **
** graph instead of having to choose Layout from the **
** main menu all over again. **
** 8. Now you can export the resulting diagram in a **
** number of formats including Scalable Vector Graphics**
** (SVG) which is an excellent format for posting on **
** your intranet. Other formats include png, pdf, jpg, **
** gif, html, bmp and wmf. **
** 9. Try out a few other layout strategies if you want, **
** but I think UML Style is best for JCL. **
** **
** SYNTAX - JCL2XML infile outfile [(options...] **
** **
** Where, **
** infile = File containing JCL. **
** outfile = Output xml and graphml path. **
** options = XML - Output infile.xml **
** GRAPHML- Output infile.graphml **
** JCL - Output infile.jcl (reconstructed **
** from the XML). **
** DUMP - List the parse tree for debugging. **
** SYSOUT - Include SYSOUT DDs in the graph. **
** DUMMY - Include DUMMY DDs in the graph. **
** INLINE - Include instream data in the graph.**
** TRACE - Trace parsing of the JCL. **
** ENCODING - Emit encoding=xxx in XML prolog. **
** WRAP n - Wrap XML output so that each line **
** is no wider than 'n' characters. **
** This is not always possible - for **
** example, very long attribute values**
** cannot be wrapped over multiple **
** lines. The default value for 'n' **
** is 255. **
** LINE - Output _line attributes containing **
** the source JCL line number. **
** ID - Output _id attributes (a unique **
** id per element). This attribute is **
** named '_id' so that it does not **
** clash with the predefined 'id' **
** attribute in the XML specification.**
** **
** You can negate any option by prefixing it **
** with NO. For example, NOXML. **
** **
** **
** LOGIC - The general approach is to: **
** 1. Scan the JCL and build an in-memory XML document. **
** 2. Walk through the in-memory XML document and create **
** another in-memory XML document containing graphml **
** markup. **
** 3. Write out the XML and graphml documents to files. **
** 4. If the JCL option is specified, write out a recon- **
** struction of the JCL from the in-memory XML data. **
** **
** AUTHOR - Andrew J. Armstrong <androidarmstrong+sf@gmail.com> **
** **
** CONTRIBUTORS - **
** ZA - Ze'ev Atlas <zatlas1@yahoo.com> **
** AF - Anne.Feldmeier@partner.bmw.de **
** HF - Herbert.Frommwieser@partner.bmw.de **
** **
** **
** HISTORY - Date By Reason (most recent at the top please) **
** -------- --- ----------------------------------------- **
** 20120316 AJA Return always (instead of exit) **
** 20120315 AJA Return if called as a subroutine else **
** exit. **
** 20111205 AJA Corrected parsing of key=(value) operands **
** where value contains quoted strings. **
** Added getSafeAttrName function to avoid **
** generating attribute names containing **
** '@', '#' or '$' characters. None is valid **
** in an XML attribute name so they are **
** translated to uppercase 'A', 'N' and 'S' **
** respectively. For example, DB# becomes dbN**
** Renamed deQuote to deNormalize and removed**
** dead code. **
** 20111109 AJA Added JES3 option so that NOJES3 can be **
** specified to prevent comments that happen **
** to look like JES3 statements being parsed.**
** 20110929 AJA Prevent INCLUDE from becoming a parent of **
** subsequent nodes. Ideally, JCL2XML should **
** expand the included JCL but that is not **
** currently implemented. **
** 20110911 AJA Clear dd for every JOB, EXEC and CNTL card**
** 20110910 AJA Fixed handling of instream data without a **
** preceding DD card. **
** 20110907 AJA Fixed handling of comments after IF, THEN **
** and ELSE statements. **
** 20110903 AJA Improved drawing of concatenated DDs. **
** 20110903 ZA Fixed getFileWithoutExtension to work when**
** dots are present in the path name. **
** 20090822 AJA Changed from GPL to BSD license. **
** 20070323 AJA Used createDocumentFragment() API. **
** 20070220 AJA Added JCL option to output a file **
** containing JCL reconstructed from the **
** in-memory XML document. This effect- **
** ively reformats your JCL file. **
** 20070215 AJA Reworked string handling. Quotes are **
** no longer stripped from quoted **
** strings. This simplifies recreation **
** of the JCL from the XML. **
** 20070208 AJA Allow numeric values to be specified **
** after command line options. This is **
** so that you can, for example, specify **
** WRAP 72 to wrap XML output lines at **
** column 72. **
** 20070130 AJA Parsed comments on ELSE statement. **
** Added LINE and ID options. . **
** 20070128 AJA Implemented output line wrapping if **
** line length exceeds the value in **
** g.0MAX_OUTPUT_LINE_LENGTH and the **
** WRAP option is specified. NOWRAP is **
** the default for all environments **
** other than TSO. **
** 20070124 AJA Only append inline data once! **
** 20070119 AJA Rework inline data processing under **
** TSO such that the CDATA section is **
** split into lines at each linefeed **
** instead of being output as a single **
** possibly very long record. **
** 20070118 AJA Write CDATA as multiple records in a **
** TSO environment. **
** 20070117 HF Added support for JES3 statements & **
** AF Tivoli Workload Scheduler (formerly **
** OPC) directives. **
** 20070117 AF Initialize g.0RC **
** 20061017 AJA Added support for UNIX environment. **
** Tested on Ubuntu Linux 6.06 LTS. **
** 20061012 AJA Preserved spaces in JCL comments. **
** 20061006 AJA Added TRACE option (default NOTRACE). **
** Added ENCODING option to allow the **
** XML prolog "encoding" attribute to be **
** suppressed using NOENCODING. **
** Added src attribute to jcl element **
** identifying the source of the JCL. **
** Added _line attribute containing the **
** line number of the first card of each **
** statement parsed. **
** 20060922 AJA Fix bug in getParmMap(). **
** 20060901 AJA Initial version. **
** **
**********************************************************************/
jcl2xml:
parse arg sFileIn sFileOut' ('sOptions')'
numeric digits 16
parse value sourceline(1) with . sVersion
say 'JCL000I JCL to XML Converter' sVersion
if sFileIn = ''
then do
say 'Syntax:'
say ' JCL2XML infile outfile [(options]'
say
say 'Where:'
say ' infile = Input job control file'
say ' outfile = Output xml and graphml path'
say ' options = XML - Output infile.xml'
say ' GRAPHML- Output infile.graphml'
say ' JCL - Output infile.jcl (reconstructed'
say ' from the XML)'
say ' DUMP - List the parse tree for debugging'
say ' SYSOUT - Include SYSOUT DDs in the graph'
say ' DUMMY - Include DUMMY DDs in the graph'
say ' INLINE - Include instream data in the graph'
say ' TRACE - Trace parsing of the JCL'
say ' ENCODING - Emit encoding="xxx" in XML prolog'
say ' WRAP n - Wrap output, where possible, to be no'
say ' wider than n characters'
say ' LINE - Output _line attributes containing'
say ' the source JCL line number'
say ' ID - Output _id attributes'
say
say ' You can negate any option by prefixing it'
say ' with NO. For example, NOXML.'
return
end
say 'JCL001I Scanning job control in' sFileIn
sOptions = 'NOBLANKS' toUpper(sOptions)
call initParser sOptions /* <-- This is in PARSEXML rexx */
g.0VERSION = sVersion
parse source g.0ENV .
if g.0ENV = 'TSO'
then do
address ISPEXEC
'CONTROL ERRORS RETURN'
g.0LINES = 0
end
call setFileNames sFileIn,sFileOut
call setOptions sOptions
call Prolog
gDoc = createDocument('graphml')
call scanJobControlFile
if g.0OPTION.GRAPHML
then do
call buildGraphML
g.0YED_FRIENDLY = 1
call prettyPrinter g.0FILEGML
end
if g.0OPTION.DUMP
then call _displayTree
if g.0OPTION.XML
then do
call setDocType /* we don't need a doctype declaration */
call setPreserveWhitespace 1 /* retain spaces in JCL comments */
if g.0OPTION.LINE = 0 /* 20070130 */
then call removeAttributes '_line',g.0JCL
if g.0OPTION.ID = 0 /* 20070130 */
then call removeAttributes '_id',g.0JCL
call rearrangeComments
g.0YED_FRIENDLY = 0
call prettyPrinter g.0FILEXML,,g.0JCL
end
if g.0OPTION.JCL
then do
call prettyJCL g.0FILEJCL,g.0JCL
end
call Epilog
say 'JCL002I Done'
return
/* The JobControl input filename is supplied by the user.
The names of the XML and GRAPHML output files are automatically
generated from the input file filename. The generated file names also
depend on the operating system. Global variables are set as follows:
g.0FILETXT = name of input text file (e.g. JobControl.txt)
g.0FILEGML = name of output GraphML file (e.g. JobControl.graphml)
g.0FILEXML = name of output XML file (e.g. JobControl.xml)
g.0FILEJCL = name of output JCL file (e.g. JobControl.jcl)
*/
setFileNames: procedure expose g.
parse arg sFileIn,sFileOut
if sFileOut = '' then sFileOut = sFileIn
if g.0ENV = 'TSO'
then do
g.0FILETXT = toUpper(sFileIn)
parse var sFileOut sDataset'('sMember')'
if pos('(',sFileOut) > 0 /* if member name notation used */
then do /* output to members in the specified PDS */
if sMember = '' then sMember = 'JCL'
sPrefix = strip(left(sMember,7)) /* room for a suffix char */
sPrefix = toUpper(sPrefix)
/* squeeze the file extension into the member name...*/
g.0FILEGML = sDataset'('strip(left(sPrefix'GML',8))')'
g.0FILEXML = sDataset'('strip(left(sPrefix'XML',8))')'
g.0FILEJCL = sDataset'('strip(left(sPrefix'JCL',8))')'
end
else do /* make output files separate datasets */
g.0FILEGML = sDataset'.GRAPHML'
g.0FILEXML = sDataset'.XML'
g.0FILEJCL = sDataset'.JCL'
end
end
else do
sFileName = getFilenameWithoutExtension(sFileOut)
g.0FILETXT = sFileIn
g.0FILEGML = sFileName'.graphml'
g.0FILEXML = sFileName'.xml'
g.0FILEJCL = sFileName'.jcl'
end
return
getFilenameWithoutExtension: procedure expose g.
parse arg sFile
nLastDot = lastpos('.',sFile)
/*ZA deal with dir with dot, file w/o dot */
nLastSlash = lastpos('\',sFile)
if nLastSlash = 0
then nLastSlash = lastpos('/',sFile)
if nLastSlash > nLastDot
then sFileName = sFile
else do
/*ZA end */
if nLastDot > 1
then sFileName = substr(sFile,1,nLastDot-1)
else sFileName = sFile
end
return sFileName
initStack: procedure expose g.
g.0T = 0 /* set top of stack index */
return
pushStack: procedure expose g.
parse arg item
tos = g.0T + 1 /* get new top of stack index */
g.0E.tos = item /* set new top of stack item */
g.0T = tos /* set new top of stack index */
return
popStack: procedure expose g.
tos = g.0T /* get top of stack index for */
item = g.0E.tos /* get item at top of stack */
g.0T = max(tos-1,1)
return item
peekStack: procedure expose g.
tos = g.0T /* get top of stack index */
item = g.0E.tos /* get item at top of stack */
return item
/*
The syntax of a single statement is like:
//name command positionals,key=value,... comment
Each statement can continue over several lines by appending a comma
and starting the continuation between columns 4 and 16:
//name command key=value, comment
// key=value, comment
// key=value comment
*/
scanJobControlFile: procedure expose g.
call initStack /* stack of conditional jcl blocks */
g.0JCL = createDocumentFragment('jcl')
call setAttribute g.0JCL,'src',g.0FILETXT
call appendAuthor g.0JCL
g.0STMTID = 0 /* unique statement id */
parent = g.0JCL
call pushStack parent
g.0FILEIN = openFile(g.0FILETXT)
g.0JCLLINE = 0 /* current line number in the JCL */
g.0DELIM = '/*' /* current end-of-data delimiter */
g.0JCLDATA.0 = 0 /* current number of lines of inline data */
g.0PENDING_STMT = ''
dd = '' /* DD associated with any inline data */
call getStatement /* returns data in g.0JCLxxxx variables */
if g.0OPTION.TRACE /* 20070130 */
then say ' Stmt Line Type Name Op Operands'
do nStmt = 1 while g.0RC = 0 & g.0JCLTYPE <> g.0JCLTYPE_EOJ
if g.0OPTION.TRACE then call sayTrace nStmt
select
when g.0JCLTYPE = g.0JCLTYPE_STATEMENT then do
stmt = newStatementNode()
select
when g.0JCLOPER = 'IF' then do
parent = popUntil('step if then else proc job')
if getNodeName(parent) = 'step'
then do
parent = popStack() /* discard 'step' */
parent = peekStack()
end
call appendChild stmt,parent
call pushStack stmt
thenNode = newPseudoStatementNode('then')
call appendchild thenNode,stmt
call pushStack thenNode
end
when g.0JCLOPER = 'ELSE' then do
parent = popUntil('if')
call appendChild stmt,parent
call pushStack stmt
end
when g.0JCLOPER = 'ENDIF' then do
parent = popUntil('if')
if g.0JCLNAME <> '' /* 20070130 */
then call setAttribute parent,'_endname',g.0JCLNAME
if g.0JCLCOMM <> '' /* 20070130 */
then call setAttribute parent,'_endcomm',g.0JCLCOMM
parent = popStack() /* discard 'if' */
end
when g.0JCLOPER = 'JOB' then do
dd = ''
parent = popUntil('jcl')
call appendChild stmt,parent
call pushStack stmt
end
when g.0JCLOPER = 'JCLLIB' then do
parent = popUntil('job')
call appendChild stmt,parent
parse var g.0JCLPARM 'ORDER='sOrder .
if left(sOrder,1) = '('
then parse var sOrder '('sOrder')'
sOrder = translate(sOrder,'',',')
g.0ORDER.0 = 0
do j = 1 to words(sOrder)
g.0ORDER.j = word(sOrder,j)
g.0ORDER.0 = j
end
/* TODO: Append system libraries somehow */
/* The JES2 search order for INCLUDE groups is:
1. // JCLLIB ORDER=(dsn,dsn...)
2. /@JOBPARM PROCLIB=ddname
...where ddname is in JES2 started task JCL.
3. JES2 initialisation parameters:
JOBCLASS(v) PROCLIB=nn
...where PROCnn DD is in JES2 started task JCL.
4. PROC00 DD in JES2 started task JCL.
*/
end
when g.0JCLOPER = 'INCLUDE' then do
/* TODO: Replace this with the actual included text */
parent = popUntil('step proc job')
call appendChild stmt,parent
end
when g.0JCLOPER = 'PROC' then do
parent = peekStack()
call appendChild stmt,parent
call pushStack stmt
end
when g.0JCLOPER = 'PEND' then do
parent = popUntil('proc')
parent = popStack() /* discard 'proc' */
end
when g.0JCLOPER = 'CNTL' then do
dd = ''
parent = popUntil('step proc job')
call appendChild stmt,parent
call pushStack stmt
end
when g.0JCLOPER = 'ENDCNTL' then do
parent = popUntil('cntl')
parent = popStack() /* discard 'cntl' */
end
when g.0JCLOPER = 'EXEC' then do
dd = ''
parent = popUntil('proc job then else')
call appendChild stmt,parent
call pushStack stmt
end
when g.0JCLOPER = 'DD' then do
dd = stmt /* used to append instream data later...*/
if getAttribute(stmt,'_name') = '' /* concatenated dd? */
then do
parent = peekStack()
call appendChild stmt,parent /* append to owning dd */
end
else do /* this is a named dd (i.e. not concatenated) */
parent = popUntil('cntl step proc job')
call appendChild stmt,parent
call pushStack stmt
end
end
when g.0JCLOPER = 'SET' then do
/* coalesce multiple 'SET' statements into a single one */
/* TODO: consider coalescing SET stmts after XML is built */
if sLastOper <> 'SET'
then do
parent = peekStack()
call appendChild stmt,parent
set = stmt
end
else do /* move 'var' nodes in this 'set' to the first */
vars = getChildNodes(stmt)
do j = 1 to words(vars)
var = word(vars,j)
varCopy = cloneNode(var) /* 20070128 */
call appendChild varCopy,set
call removeChild var
end
call appendChild stmt,parent /* kludge... */
call removeChild stmt /* ...to allow infanticide! */
end
end
otherwise do /* all other statements cannot be parents */
parent = peekStack()
call appendChild stmt,parent
end
end
sLastOper = g.0JCLOPER
end
when g.0JCLTYPE = g.0JCLTYPE_DATA then do
if dd = '' /* inline data without a preceding dd */
then do /* auto-generate a SYSIN DD statement */
g.0STMTID = g.0STMTID + 1
dd = newElement('dd','_id',g.0STMTID,,
'_line',g.0JCLLINE,,
'_name','SYSIN',,
'_','*',,
'_comment','GENERATED STATEMENT')
parent = popUntil('cntl step proc job')
call appendChild dd,parent
call pushStack dd
end
call appendChild getInlineDataNode(),dd
g.0JCLDATA.0 = 0 /* 20070124 */
end
when g.0JCLTYPE = g.0JCLTYPE_COMMENT then do
if nLastStatementType <> g.0JCLTYPE_COMMENT
then do /* group multiple comment lines together */
comment = newElement('comment','_line',g.0JCLLINE)
parent = popUntil('step proc job dd if then else') /* 20110907 */
call appendChild comment,parent
end
call appendChild createTextNode(g.0JCLCOMM),comment
end
when g.0JCLTYPE = g.0JCLTYPE_JES2CMD then do
stmt = newStatementNode()
parent = peekStack()
call appendChild stmt,parent
end
when g.0JCLTYPE = g.0JCLTYPE_JES2STMT then do
stmt = newStatementNode()
parent = peekStack()
call appendChild stmt,parent
end
when g.0JCLTYPE = g.0JCLTYPE_JES3STMT then do
stmt = newStatementNode()
parent = peekStack()
call appendChild stmt,parent
end
when g.0JCLTYPE = g.0JCLTYPE_OPCDIR then do /* HF 061218 */
stmt = newStatementNode()
parent = peekStack()
call appendChild stmt,parent
end /* HF 061218 */
when g.0JCLTYPE = g.0JCLTYPE_EOJ then nop
otherwise do /* should not occur (famous last words) */
say 'JCL003E Unknown statement on line' g.0JCLLINE':',
'"'g.0JCLOPER g.0JCLPARM'"'
end
end
nLastStatementType = g.0JCLTYPE
call getStatement
end
if g.0JCLDATA.0 > 0 /* dump any pending sysin before eof reached */
then do
call appendChild getInlineDataNode(),dd
end
rc = closeFile(g.0FILEIN)
say 'JCL004I Processed' g.0K-1 'JCL statements'
return
sayTrace: procedure expose g.
parse arg nStmt
select
when g.0JCLTYPE = g.0JCLTYPE_COMMENT then do
call sayTraceLine nStmt,g.0JCLLINE,g.0JCLCOMM
end
when g.0JCLTYPE = g.0JCLTYPE_DATA then do
nLine = g.0JCLLINE - g.0JCLDATA.0
do i = 1 to g.0JCLDATA.0
call sayTraceLine nStmt,nLine,g.0JCLDATA.i
nLine = nLine + 1
end
end
otherwise do
call sayTraceLine nStmt,g.0JCLLINE,,
left(g.0JCLNAME,8) left(g.0JCLOPER,8) g.0JCLPARM
end
end
return
sayTraceLine: procedure expose g.
parse arg nStmt,nLine,sData
nType = g.0JCLTYPE
sType = g.0JCLTYPE.nType
say left(right(nStmt,5) right(nLine,5) left(sType,4) sData,79)
return
appendAuthor: procedure expose g.
parse arg node
comment = createComment('Created by JCL to XML Converter' g.0VERSION)
call appendChild comment,node
comment = createComment('by Andrew J. Armstrong',
'(androidarmstrong@gmail.com)')
call appendChild comment,node
return
removeAttributes: procedure expose g.
parse arg sAttrName,node
if isElementNode(node)
then call removeAttribute node,sAttrName
children = getChildNodes(node)
do i = 1 to words(children)
child = word(children,i)
call removeAttributes sAttrName,child
end
return
/*
Usually, comments precede a step in JCL. However, the XML built by
this scanner will associate comments with the previous step. The
intention of the following routine is to move step comments to their
rightful position - under that step's node. It's not foolproof though.
*/
rearrangeComments: procedure expose g.
steps = getElementsByTagName(g.0JCL,'step')
do i = 1 to words(steps)
step = word(steps,i)
prev = getPreviousSibling(step)
if prev <> ''
then do
if getNodeName(prev) = 'comment'
then call moveNode prev,step
else do
node = getLastElementDescendant(prev)
if node <> ''
then do
if getNodeName(node) = 'comment'
then call moveNode node,step
end
end
end
end
return
getLastElementDescendant: procedure expose g.
parse arg node
child = getLastChild(node)
if isElementNode(child)
then decendant = getLastElementDescendant(child)
else decendant = node
return decendant
moveNode: procedure expose g.
parse arg original,newParent
node = removeChild(original)
firstChild = getFirstChild(newParent)
if firstChild <> ''
then call insertBefore node,firstChild
else call appendChild node,newParent
return
popUntil: procedure expose g.
parse arg sNodeNames
node = peekStack()
do while wordpos(getNodeName(node),sNodeNames 'jcl') = 0
node = popStack()
node = peekStack()
end
return node
/*
The function of this routine is to return the next statement (by
accumulating continuations if necessary). The statement is returned in
a set of global variables as follows:
g.0JCLLINE = the line number of the first card of the statement
g.0JCLTYPE = the type of statement as follows:
g.0JCLTYPE_UNKNOWN - An unknown statement
g.0JCLTYPE_STATEMENT - A JCL statement
g.0JCLTYPE_DATA - Instream data (see below)
g.0JCLTYPE_COMMENT - A comment card
g.0JCLTYPE_EOJ - An end-of-job card
g.0JCLTYPE_JES2CMD - A JES2 command
g.0JCLTYPE_JES2STMT - A JES2 statement
g.0JCLTYPE_JES3STMT - A JES3 statement
g.0JCLTYPE_OPCDIR - An OPC directive
g.0JCLNAME = the statement name field (e.g. a dd name)
g.0JCLOPER = the statement operation field (e.g. DD)
g.0JCLPARM = the statement parameters (e.g. DISP=SHR etc)
g.0JCLCOMM = any comment following the statement, or the entire
comment text if this is a comment card
One or more instream data cards are treated as a single pseudo-statement
with g.0JCLTYPE set to g.0JCLTYPE_DATA and the cards returned in an
array as follows:
g.0JCLDATA.0 = the number of instream data cards
g.0JCLDATA.n = instream data card 'n' (n = 1 to g.0JCLDATA.0)
*/
getStatement: procedure expose g.
g.0JCLTYPE = g.0JCLTYPE_UNKNOWN
g.0JCLNAME = '' /* Statement label */
g.0JCLOPER = '' /* Statement operation */
g.0JCLCOMM = '' /* Statement comment */
g.0RC = 0 /* AF 061018 */
/* The following kludge handles the case where a JCL author
omits the end-of-data delimiter from inline data (instead the
next statement terminates the inline data). When this happens we
have already read the next statement, so we need to remember it
and process it the next time 'getStatement' is called.
*/
if g.0PENDING_STMT <> ''
then do
sLine = g.0PENDING_STMT
g.0PENDING_STMT = ''
end
else sLine = getNextLine()
parse var sLine 1 s2 +2 1 s3 +3
select
when s2 = '/*' & substr(sLine,3,1) <> ' ' then do
if substr(sLine,3,1) = '$'
then do
parse var sLine '/*$'sJesCmd','sJesParms
g.0JCLTYPE = g.0JCLTYPE_JES2CMD
g.0JCLOPER = sJesCmd
g.0JCLPARM = sJesParms
end
else do
parse var sLine '/*'sJesStmt sJesParms
g.0JCLTYPE = g.0JCLTYPE_JES2STMT
g.0JCLOPER = sJesStmt
g.0JCLPARM = sJesParms
end
end
when s3 = '//*' then do /* HF 061218 */
/* This statement may be a comment or a JES3 command or an OPC */
/* directive or ...... */
/* So let's make the distinction here... */
sWord = substr(word(sLine,1),4)
select
when sWord = '%OPC' then do /* OPC directive */
parse var sLine '//*%OPC' sOpcStmt sOpcParms
g.0JCLTYPE = g.0JCLTYPE_OPCDIR
g.0JCLOPER = sOpcStmt
g.0JCLPARM = sOpcParms
end
when isJes3Statement(sWord) then do /* JES3 statement */
parse var sLine '//*'sJesStmt sJesParms
g.0JCLTYPE = g.0JCLTYPE_JES3STMT
g.0JCLOPER = sJesStmt
g.0JCLPARM = sJesParms
end
otherwise do /* Comment */
g.0JCLTYPE = g.0JCLTYPE_COMMENT
g.0JCLCOMM = substr(sLine,4)
end
end
end /* HF 061218 */
when sLine = '//' then do
g.0JCLTYPE = g.0JCLTYPE_EOJ
end
when s2 = '//' then do
sName = ''
if substr(sLine,3,1) = ' '
then parse var sLine '//' sOper sParms
else parse var sLine '//'sName sOper sParms
g.0JCLTYPE = g.0JCLTYPE_STATEMENT
g.0JCLNAME = sName
g.0JCLOPER = sOper
select /* 20070130 */
when sOper = 'IF' then do
/* IF has its own continuation rules */
do while g.0RC = 0 & pos('THEN',sParms) = 0
sLine = getNextLine()
parse var sLine '//' sThenContinued
sParms = sParms strip(sThenContinued)
end
parse var sParms sParms 'THEN' sComment
g.0JCLPARM = strip(sParms)
g.0JCLCOMM = sComment /* 20070128 */
end
when sOper = 'ELSE' | sOper = 'ENDIF' then do /* 20070130 */
g.0JCLPARM = ''
g.0JCLCOMM = strip(sParms)
end
otherwise do /* Slurp up any continuation cards */
/* This gets really ugly...
Lines are considered to be continued when they end in a
comma, or a comma followed by a comment, or if a quoted
string has not been terminated by another quote.
For exmample:
//STEP1 EXEC PGM=IEFBR14,
// PARM='HI THERE'
OR
//STEP1 EXEC PGM=IEFBR14, A COMMENT
// PARM='HI THERE'
OR
//STEP1 EXEC PGM=IEFBR14,PARM='HI
// THERE' A COMMENT
Comment statements in a continuation are ignored:
//STEP1 EXEC PGM=IEFBR14,
//.A comment (star is shown as a dot to keep Rexx happy)
// PARM='HI THERE'
*/
g.0INSTRING = 0 /* for detecting continued quoted strings */
sParms = getNormalized(sParms)
parse var sParms sParms sComment
g.0JCLPARM = sParms
do while g.0RC = 0 & pos(right(sParms,1),'ff'x',') > 0
sLine = getNextLine()
do while g.0RC = 0 & left(sLine,3) = '//*'
sLine = getNextLine()
end
if g.0RC = 0
then do
parse var sLine '//' sParms
sParms = getNormalized(sParms)
parse var sParms sParms sComment
g.0JCLPARM = g.0JCLPARM || sParms
end
end
if sOper = 'DD' & pos('DLM=',g.0JCLPARM) > 0
then parse var g.0JCLPARM 'DLM=' +4 g.0DELIM +2
g.0JCLCOMM = sComment /* 20070128 */
end
end
end
otherwise do
g.0JCLTYPE = g.0JCLTYPE_DATA
g.0JCLPARM = ''
n = 0
do while g.0RC = 0 & \isEndOfData(s2)
n = n + 1
g.0JCLDATA.n = strip(sLine,'TRAILING')
sLine = getNextLine()
parse var sLine 1 s2 +2
end
if g.0DELIM = '/*' & s2 = '//' /* end-of-data marker omitted */
then g.0PENDING_STMT = sLine
g.0JCLDATA.0 = n
g.0DELIM = '/*' /* reset EOD delimiter to the default */
end
end
g.0K = g.0K + 1
g.0KDELTA = g.0KDELTA + 1
if g.0KDELTA >= 100
then do
say 'JCL005I Processed' g.0K 'statements'
g.0KDELTA = 0
end
return
isJes3Statement: procedure expose g.
arg sStmt
if \g.0OPTION.JES3 then return 0
sJes3Stmts = 'DATASET ENDDATASET ENDPROCESS FORMAT MAIN NET NETACCT',
'OPERATOR *PAUSE PROCESS ROUTE'
return wordpos(sStmt,sJes3Stmts) > 0
/* Replace blanks in quoted strings with 'ff'x so it is easier
to parse later. For example:
<---parameters----><----comments------>
this: ABC,('D E F','GH'), 'QUOTED COMMENT'
becomes: ABC,('D~E~F','GH'), 'QUOTED COMMENT'
Where '~' is a 'hard' blank ('ff'x)
*/
getNormalized: procedure expose g.
parse arg sLine
sLine = strip(sLine,'LEADING')
sNormalized = ''
do i = 1 to length(sLine) until c = ' ' & \g.0INSTRING
c = substr(sLine,i,1)
select
when c = "'" & g.0INSTRING then g.0INSTRING = 0
when c = "'" then g.0INSTRING = 1
when c = ' ' & g.0INSTRING then c = 'ff'x
otherwise nop
end
sNormalized = sNormalized || c
end
if i <= length(sLine)
then do
if g.0INSTRING /* make trailing blanks 'hard' blanks */
then sNormalized = sNormalized ||,
translate(substr(sLine,i),'ff'x,' ')
else sNormalized = sNormalized || substr(sLine,i)
end
return strip(sNormalized)
isEndOfData: procedure expose g.
parse arg s2
bEOD = g.0DELIM = s2,
| (g.0DELIM = '/*' & s2 = '//')
return bEOD
getInlineDataNode: procedure expose g.
sLines = ''
do n = 1 to g.0JCLDATA.0
sLines = sLines || g.0JCLDATA.n || g.0LF
end
return createCDATASection(sLines)
newPseudoStatementNode: procedure expose g.
parse arg sName
g.0STMTID = g.0STMTID + 1
stmt = newElement(sName,'_id',g.0STMTID)
return stmt
/*
This is a helper routine that creates a named element and
optionally sets one or more attributes on it. Note Rexx only allows
up to 20 arguments to be passed.
*/
newElement: procedure expose g.
parse arg sName /* attrname,attrvalue,attrname,attrvalue,... */
id = createElement(sName)
do i = 2 to arg() by 2
call setAttribute id,arg(i),arg(i+1)
end
return id
newStatementNode: procedure expose g.
g.0STMTID = g.0STMTID + 1
select
when g.0JCLTYPE = g.0JCLTYPE_JES2CMD then do
stmt = newElement('jes2cmd',,
'_id',g.0STMTID,,
'_line',g.0JCLLINE,,
'cmd',g.0JCLOPER,,
'parm',strip(g.0JCLPARM))
return stmt
end
when g.0JCLTYPE = g.0JCLTYPE_JES2STMT then do
stmt = newElement('jes2stmt',,