MakeXml

From Pickwiki
Revision as of 23:48, 26 February 2015 by Conversion script (talk) (link fix)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigationJump to search

Back to BasicSource

This program generates XML using the standard dictionaries. One of the nice bits is that it can use phrases to reduce the input line size and to bundle stuff into sub-elements.

PROGRAM MAKEXML
* ECL - KRJ - Generates XML using standard dictionaries
*
**** NOTE - ECL is what Unidata called TCL - it's not a company name,
****        it's advising that this is a command line utility.
****        KRJ are my iniials - Keith Robert Johnson. I wrote most of it, you use it at your own risk.
****        SKR is Shayne Riches - the 10 line GRAPH subroutine is his, I think I asked him if I could
****        use it, but it was so long ago I can't remember exactly.  Don't use it if you don't want to.
****        The rest of this program is Public Domain, you can do what you want with it - even change the
****        source if you find a bug (just not just because you don't like my style - now please!)
****
* You have to make the WRITESEQ lines work for the U2 brand
* Unidata - WRITESEQ LINE APPEND ON ...
* Universe & QM - WRITESEQ LINE ON ...
*
* Next lines for QM
$MODE UV.LOCATE
      EQU ITATT TO 16
* Next lines for Universe
* $OPTIONS PICK.FORMAT -STATIC.DIM
* EQU ITATT TO 20
* Next lines for Unidata
* $BASICTYPE 'P'
* EQU ITATT TO 8
*
      VERSION = 'xml version="1.0"'
      COMD = 'MAKEXML'
      SENTENCE = OCONV(@SENTENCE,'MCU')
      TEST = INDEX(SENTENCE,COMD,1)
      IF TEST THEN
         SENTENCE = TRIM(@SENTENCE[TEST+LEN(COMD),LEN(@SENTENCE)])
      END ELSE SENTENCE = @SENTENCE
      OPTIONS = FIELD(SENTENCE,'(',2)
      SENTENCE = FIELD(SENTENCE,'(',1)
      OPTIONS = OCONV(FIELD(OPTIONS,')',1),'MCU')
*
      ESC = CHAR(27)
      AMPERSAND = CHAR(38)
      T1 = CHAR(9)
      T2 = T1:T1
      T3 = T1:T1:T1
      BELL = STR(CHAR(7),20)
      BAD = @TRUE
      FIRST = @TRUE
* Get the operating system (text) file we want to create - and where from
      [[TYPE19FILE]] = FIELD(SENTENCE,' ',1)
      [[TYPE19ITEM]] = FIELD(SENTENCE,' ',2)
      FNAME = FIELD(SENTENCE,' ',3)
      DICTS = TRIM(SENTENCE[COL2(),LEN(SENTENCE)])
* If we haven't got the data source, give help
      IF FNAME EQ '' THEN
         CRT @(-1):
         CRT COMD:' - Generates XML'
         CRT
         CRT 'SYNTAX - ':COMD:' type19file item file [fields] (Options)'
         CRT
         CRT 'OPTIONS'
         CRT ' G - Generate a schema only (.XSD)'
         CRT ' D - DTD output only (.DTD)'
         CRT ' K - Kill empty attributes (IE ignore them in XML)'
         CRT ' Q - Quiet, do not display progress information'
         CRT ' X - XSL outout required (This creates XML as well)'
         CRT
         CRT 'NOTES'
         CRT 'If type19file is "CRT" output is to the screen.'
         CRT 'The output file will have extensions added to suit the type'
         CRT 'of output requested. If XSL file output is requested, then'
         CRT 'BOTH the XSL and XML outputs are produced.'
         STOP
      END
*
* Get the options
      IF INDEX(OPTIONS,'Q',1) THEN QUIET = @TRUE ELSE QUIET = @FALSE
      IF INDEX(OPTIONS,'K',1) THEN KILL = @TRUE ELSE KILL = @FALSE
      OUTPUT = 'XML'
      IF INDEX(OPTIONS,'D',1) THEN OUTPUT = 'DTD'
      IF INDEX(OPTIONS,'G',1) THEN OUTPUT = 'XSD'
      IF INDEX(OPTIONS,'X',1) THEN OUTPUT = 'XSL'
      IF OUTPUT NE 'XML' THEN QUIET = @TRUE
      IF @TTY EQ 'phantom' THEN QUIET = @TRUE
* Check if we have got dictionary items
      IF DICTS EQ '' THEN
         CRT 'Dictionary Items are required for XML'
         STOP
      END
*
* Display graph if terminal and select list is active
      TOTCNT = SYSTEM(11)
* Next line for Universe
      IF TOTCNT THEN TOTCNT = @SELECTED
*
      CNTR = 0
      OLD.PCT = 0
      SHOW = @FALSE
      IF TOTCNT THEN SHOW = @TRUE
      IF QUIET THEN SHOW = @FALSE
*
* Open, check, and initialise the transfer file
      IF [[TYPE19FILE]] NE 'CRT' THEN
         OPEN [[TYPE19FILE]] TO TRANSFER.FILE ELSE
            CRT 'Cannot open file ':[[TYPE19FILE]]:BELL
            STOP
         END
* I THINK the next line will work in Unidata
         IF FILEINFO(TRANSFER.FILE,3) NE '4' THEN
            CRT [[TYPE19FILE]]:' is not type 1 or 19 file (Directory)':BELL
            STOP
         END
         DELETE TRANSFER.FILE, [[TYPE19ITEM]]:'.':OUTPUT
         IF OUTPUT = 'XSL' THEN DELETE TRANSFER.FILE, [[TYPE19ITEM]]:'.XML'
      END ELSE SHOW = @FALSE; QUIET = @TRUE
*
* Open the source file and its dictionary
      OPEN FNAME TO IFILE ELSE
         CRT 'Cannot open file ':FNAME:BELL
         STOP
      END
      OPEN 'DICT',FNAME TO DFILE ELSE
         CRT 'Cannot open the dictionary of file ':FNAME:BELL
         STOP
      END
*
* Make sure the name of the file is good XML
      XMLNAME = FNAME
      CONVERT ' ':CHAR(9) TO '' IN XMLNAME
      FRAG = XMLNAME; GOSUB CHANGE.CHARS; XMLNAME = FRAG
*
* Build up the dictionary data
      FIELD.LIST = CONVERT(' ',@AM,DICTS)
      XXCNT = DCOUNT(FIELD.LIST,@AM)
*
* Pass through the dictionary list, expanding any phrases
* and setting out XML attributes, multivalues, and associated (or grouped)
* U2 attributes.
      PHRASES = ''
      ATTS = ''
      TAGS = ''
      GRPS = ''
      GGCNT = 0
      GROUPS = ''
      GRP.TITLE = ''
      FOR XX = 1 TO XXCNT
         ID = FIELD.LIST<XX>
         READ DREC FROM DFILE,ID THEN
            IF OCONV(DREC[1,2],'MCU') EQ 'PH' THEN
               LOCATE ID IN PHRASES SETTING POSN THEN
                  CRT 'Circular reference - ':
                  CRT ID:' is a phrase that has been used before.'
                  STOP
               END
               PHRASES<-1> = ID
               NEWBIT = CONVERT(' ',@AM,DREC<2>)
               YYCNT = DCOUNT(NEWBIT,@AM)
               FIELD.LIST<XX> = NEWBIT
               XX = XX - 1
               XXCNT = XXCNT + YYCNT - 1
            END ELSE
               BEGIN CASE
                  CASE DREC<1>[1,1] = 'A' OR DREC<6> = 'S'
                     ATTS<-1> = XX
* NOTE - I can't remember how Unidata handles groups
                  CASE DREC<6> = 'M' AND DREC<7> NE ''
                     GNAME = DREC<7>
* Only allow two words maximum as a group title
* The first word will be a sort of wrapper for the second, if there is one
                     GNAME = TRIM(GNAME)
                     TEST = INDEX(GNAME,' ',2)
                     IF TEST THEN GNAME = GNAME[1,TEST-1]
                     LOCATE GNAME IN GROUPS SETTING POSN THEN
                        GRPS<POSN,-1> = XX
                     END ELSE
                        GGCNT = GGCNT + 1
                        GROUPS<GGCNT> = GNAME
                        FRAG = GNAME; GOSUB CHANGE.CHARS
                        GRP.TITLE<GGCNT> = FRAG
                        GRPS<GGCNT> = XX
                     END
* Multivalues are set out as elements, and require tags
                  CASE DREC<6> = 'M'
                     TAGS<-1> = XX
                  CASE 1
                     CRT ID:' is an invalid dictionary field'
                     STOP
               END CASE
            END
         END
      NEXT XX
*
* Dimension and initialise the dictionary stuff
      DIM DICT.RECS(XXCNT); MAT DICT.RECS = ''
      DIM DICT.CONV(XXCNT); MAT DICT.CONV = ''
      DIM DICT.TYPE(XXCNT); MAT DICT.TYPE = ''
      DIM DICT.TITL(XXCNT); MAT DICT.TITL = ''
*
* Process the dictionary data
      FOR XX = 1 TO XXCNT
         ID = FIELD.LIST<XX>
         READ DREC FROM DFILE, ID THEN
            DTYPE = DREC<1>[1,1]
            IF DTYPE EQ 'V' THEN DTYPE = 'I'
* Convert A-type to D-type - our software can have conversion on 8 or 7
            IF DTYPE = 'A' THEN
               TEMP = 'D':@AM:DREC<2>:@AM:DREC<8>:@AM:DREC<3>
               TEMP<5> = DREC<10>:DREC<9>
               TEMP<6> = 'S'
               IF DREC<7> NE '' THEN TEMP<3> = DREC<7>
               DREC = TEMP
               DTYPE = 'D'
            END
* Check the dictionary is compiled
            IF DTYPE EQ 'I' AND DREC<ITATT> = '' THEN
               EXECUTE 'COMPILE.DICT ':FNAME:' ':ID CAPTURING JUNK
               READ DREC FROM DFILE, ID ELSE DREC = ''
               IF DREC<ITATT> EQ '' THEN
                  CRT 'Cannot compile dictionary ':ID
                  STOP
               END
            END
            IF DTYPE EQ 'D' OR DTYPE EQ 'I' THEN
               BAD = @FALSE
               DICT.RECS(XX) = DREC
               DICT.CONV(XX) = DREC<3>
               TITL = ID
               FRAG = TITL; GOSUB CHANGE.CHARS; TITL = FRAG
               DICT.TITL(XX) = TITL
               DICT.TYPE(XX) = DTYPE
            END
         END
      NEXT XX
      IF BAD THEN
         CRT BELL:'No Dictionary items chosen':BELL
         STOP
      END
*
* See what the maximum number in a group is and dimension group workspace
      GMAX = 1
      FOR GG = 1 TO GGCNT
         GNUM = DCOUNT(GRPS<GG>,@VM)
         IF GNUM GT GMAX THEN GMAX = GNUM
      NEXT GG
      DIM GDAT(GMAX)
*
* Now generate the output file
      BEGIN CASE
         CASE OUTPUT = 'DTD'; GOSUB OUTPUT.DTD
         CASE OUTPUT = 'XSD'; GOSUB OUTPUT.SCHEMA
         CASE OUTPUT = 'XSL'
            GOSUB OUTPUT.XSL
            IF [[TYPE19FILE]] NE 'CRT' THEN
               CLOSESEQ DEST ON ERROR CRT 'CLOSESEQ ERROR':BELL
               FIRST = @TRUE
               GOSUB OUTPUT.XML
            END
         CASE 1
            GOSUB OUTPUT.XML
      END CASE
      IF [[TYPE19FILE]] NE 'CRT' THEN
*        CLOSESEQ DEST ON ERROR CRT 'CLOSESEQ ERROR':BELL
CLOSESEQ DEST
      END
      STOP
*
*
*********************************************************************
* SUBROUTINES
*********************************************************************
PACK.ITEM:
**********
      REC = @RECORD
      LINE = ''
      XMLID = @ID
      FRAG = XMLID; GOSUB CHANGE.CHARS; XMLID = FRAG
      LINE = '<item id="':XMLID:'"'
      YYCNT = DCOUNT(ATTS,@AM)
      FOR YY = 1 TO YYCNT
         XX = ATTS<YY>
         IF DICT.TYPE(XX) EQ 'D' THEN
            IF DICT.RECS(XX)<2> EQ '0' THEN
               BIT = @ID
* Just hope it is single valued
            END ELSE BIT = REC<DICT.RECS(XX)<2>>
         END ELSE
            BIT = ITYPE(DICT.RECS(XX))
         END
         IF DICT.CONV(XX) NE '' THEN BIT = OCONV(BIT,DICT.CONV(XX))
         IF KILL AND BIT EQ '' ELSE
            FRAG = BIT; GOSUB CHANGE.CHARS; BIT = FRAG
            LINE := ' ':DICT.TITL(XX):'="':BIT:'"'
         END
      NEXT YY
      LINE := '>'
      GOSUB WRITE.LINE; IF BAD THEN RETURN
*
      YYCNT = DCOUNT(TAGS,@AM)
      FOR YY = 1 TO YYCNT
         XX = TAGS<YY>
         IF DICT.TYPE(XX) EQ 'D' THEN
            IF DICT.RECS(XX)<2> EQ '0' THEN
               BIT = @ID
            END ELSE BIT = REC<DICT.RECS(XX)<2>>
         END ELSE
            BIT = ITYPE(DICT.RECS(XX))
         END
         BIT = RAISE(BIT)
         ZZCNT = DCOUNT(BIT,@AM)
         FOR ZZ = 1 TO ZZCNT
* Note how subvalues are ignored - my decision, you may disagree
            ZIT = BIT<ZZ,1>
            IF DICT.CONV(XX) NE '' THEN ZIT = OCONV(ZIT,DICT.CONV(XX))
            FRAG = ZIT; GOSUB CHANGE.CHARS; ZIT = FRAG
            ZIT = '<':DICT.TITL(XX):'>':ZIT:'</':DICT.TITL(XX):'>'
            LINE = T1:ZIT
            GOSUB WRITE.LINE; IF BAD THEN RETURN
         NEXT ZZ
      NEXT YY
*
* Process the grouped data
      FOR GG = 1 TO GGCNT
         MAT GDAT = ''
         IICNT = 0
         HHCNT = DCOUNT(GRPS<GG>,@VM)
         FOR HH = 1 TO HHCNT
            XX = GRPS<GG,HH>
            IF DICT.TYPE(XX) EQ 'D' THEN
               IF DICT.RECS(XX)<2> EQ '0' THEN
                  BIT = @ID
               END ELSE BIT = REC<DICT.RECS(XX)<2>>
            END ELSE
               BIT = ITYPE(DICT.RECS(XX))
            END
            GDAT(HH) = RAISE(BIT)
            INUM = DCOUNT(GDAT(HH),@AM)
            IF INUM GT IICNT THEN IICNT = INUM
         NEXT HH
         WRAPPER = ''
         TITLE = GRP.TITLE<GG>
         WIT = ''
         IF INDEX(TITLE,' ',1) THEN
            WRAPPER = FIELD(TITLE,' ',1)
            TITLE = FIELD(TITLE,' ',2)
            WIT = T1
         END
* Cater for the special case where there is only one in the group
* when we have an enclosing wrapper rather than a repeating title
         IF HHCNT EQ 1 AND WRAPPER EQ '' THEN
            WRAPPER = TITLE
            TITLE = ''
         END
         SAVETITLE = TITLE
         SAVEWRAPPER = WRAPPER
         FOR II = 1 TO IICNT
            TITLE = SAVETITLE
            FOR HH = 1 TO HHCNT
               XX = GRPS<GG,HH>
* Again, subvalues are ignored
               ZIT = GDAT(HH)<II,1>
               IF DICT.CONV(XX) NE '' THEN ZIT = OCONV(ZIT,DICT.CONV(XX))
               IF ZIT NE '' THEN
                  IF WRAPPER NE '' THEN
                     LINE = T1:'<':WRAPPER:'>'
                     GOSUB WRITE.LINE; IF BAD THEN RETURN
                     WRAPPER = ''
                  END
                  IF TITLE NE '' THEN
                     LINE = WIT:T1:'<':TITLE:'>'; TITLE = ''
                     GOSUB WRITE.LINE; IF BAD THEN RETURN
                  END
                  FRAG = ZIT; GOSUB CHANGE.CHARS; ZIT = FRAG
                  ZIT = '<':DICT.TITL(XX):'>':ZIT:'</':DICT.TITL(XX):'>'
                  LINE = WIT:T2:ZIT
                  GOSUB WRITE.LINE; IF BAD THEN RETURN
               END
            NEXT HH
            IF TITLE EQ '' AND SAVETITLE NE '' THEN
               LINE = WIT:T1:'</':SAVETITLE:'>'
               GOSUB WRITE.LINE; IF BAD THEN RETURN
            END
         NEXT II
         IF WRAPPER EQ '' AND SAVEWRAPPER NE '' THEN
            LINE = T1:'</':SAVEWRAPPER:'>'
            GOSUB WRITE.LINE; IF BAD THEN RETURN
         END
      NEXT GG
      LINE = '</item>'
      GOSUB WRITE.LINE
      RETURN
*
OUTPUT.XML:
***********
      IF NOT(QUIET) THEN CRT @(-1):COMD; CRT; CRT
      IF OUTPUT = 'XSL' THEN
         LINE = '<?':VERSION:'?>'
         OUTPUT = 'XML'
         GOSUB WRITE.LINE; IF BAD THEN STOP
         LINE = '<?xml-stylesheet type="text/xsl" href="'
         LINE := [[TYPE19ITEM]]:'.XSL"?>'
      END ELSE
         LINE = '<?':VERSION:' standalone="yes"?>'
      END
      GOSUB WRITE.LINE; IF BAD THEN STOP
      LINE = T1:'<!-- XML from ':@PATH:' at ':TIMEDATE()
      LINE := ' by ':@LOGNAME:' - Original line as below -->'
      GOSUB WRITE.LINE; IF BAD THEN STOP
      LINE = T1:'<!-- ':@SENTENCE:' -->'
      GOSUB WRITE.LINE; IF BAD THEN STOP
      LINE = '<file id="':XMLNAME:'">'
      GOSUB WRITE.LINE; IF BAD THEN STOP
      IF NOT(TOTCNT) THEN SELECT IFILE
      EOF = @FALSE
      LOOP
         READNEXT @ID ELSE EOF = @TRUE
      UNTIL EOF
*LOOP WHILE READNEXT @ID DO
         CNTR += 1
         IF SHOW THEN
            NEW.PCT = INT((CNTR*100)[[/TOTCNT]])
            IF NEW.PCT NE OLD.PCT THEN
               percent = NEW.PCT
               caption = 'PREPARING FILE "':[[TYPE19ITEM]]:'"'
               GOSUB GRAPH
            END
            OLD.PCT = NEW.PCT
         END ELSE
            IF NOT(QUIET) THEN
               IF NOT(REM(CNTR,100)) THEN CRT @(0):CNTR:
            END
         END
         READ @RECORD FROM IFILE, @ID THEN GOSUB PACK.ITEM
         IF BAD THEN STOP
      REPEAT
      LINE = '</file>'; GOSUB WRITE.LINE; IF BAD THEN STOP
      IF NOT(SHOW OR QUIET) THEN CRT @(-1):CNTR:
      RETURN
*
OUTPUT.DTD:
***********
      LINES = ''
      LINES<-1> = '<?':VERSION:' standalone="yes"?>'
      LINES<-1> = '<!--'
      LINES<-1> = T1:'This is the DTD for the command'
      LINES<-1> = T1:@SENTENCE
      LINES<-1> = '-->'
      LINES<-1> = '<!ELEMENT file (item)>'
      LINES<-1> = '<!ATTLIST file id CDATA #REQUIRED>'
      LINES<-1> = '<!ELEMENT item'
      BIT = ' ('
* Show tags
      YYCNT = DCOUNT(TAGS,@AM)
      FOR YY = 1 TO YYCNT
         XX = TAGS<YY>
         LINES := BIT:DICT.TITL(XX):'*'
         BIT = ', '
      NEXT YY
* Show Groups
      YYCNT = DCOUNT(GROUPS,@AM)
      FOR YY = 1 TO YYCNT
         LINES := BIT:FIELD(GROUPS<YY>,' ',1):'*'
         BIT = ', '
      NEXT YY
      IF BIT = ', ' THEN LINES := ')'
      LINES := '>'
* Show id
      LINES<-1> = '<!ATTLIST item id':T1:'CDATA #REQUIRED'
      YYCNT = DCOUNT(ATTS,@AM)
      IF YYCNT THEN
* Loop doing each ATT
         FOR YY = 1 TO YYCNT
            XX = ATTS<YY>
            LINES<-1> = SPACE(15):DICT.TITL(XX):T1:'CDATA #'
            IF KILL THEN LINES := 'IMPLIED' ELSE LINES := 'REQUIRED'
            IF YY EQ YYCNT THEN LINES := '>'
         NEXT YY
      END ELSE
         LINES := '>'
      END
* Loop doing each TAG
      YYCNT = DCOUNT(TAGS,@AM)
      FOR YY = 1 TO YYCNT
         XX = TAGS<YY>
         LINES<-1> = '<!ELEMENT ':DICT.TITL(XX):' (#PCDATA)>'
      NEXT YY
* Loop doing each group item
      GGCNT = DCOUNT(GRPS,@AM)
      FOR GG = 1 TO GGCNT
         GNAME = GROUPS<GG>
         IF INDEX(GNAME,' ',1) THEN
            LINES<-1> = '<!ELEMENT ':FIELD(GNAME,' ',1):' ('
            LINES := FIELD(GNAME,' ',2):'*)>'
            GNAME = FIELD(GNAME,' ',2)
         END
         HHCNT = DCOUNT(GRPS<GG>,@VM)
         LINES<-1> = '<!ELEMENT ':GNAME:' '
         BIT = '('
         FOR HH = 1 TO HHCNT
            XX = GRPS<GG,HH>
            LINES := BIT:DICT.TITL(XX):'*'
            BIT = ', '
         NEXT HH
         LINES := ')>'
         FOR HH = 1 TO HHCNT
            XX = GRPS<GG,HH>
            LINES<-1> = '<!ELEMENT ':DICT.TITL(XX):' (#PCDATA)>'
         NEXT HH
      NEXT GG
      GOSUB WRITE.LINES
      RETURN
*
OUTPUT.SCHEMA:
**************
      LINES = ''
      LINES<-1> = '<?':VERSION:' standalone="yes"?>'
      LINES<-1> = @AM
* Documentation
      LINES<-1> = '<xsd:schema '
      LINES := 'xmlns:xsd="http://www.w3.org/2000/10/XMLSchema">'
      LINES<-1> = '<xsd:annotation>'
      LINES<-1> = T1:'<xsd:documentation>'
      LINES<-1> = T2:'This is the SCHEMA for the command'
      LINES<-1> = T2:@SENTENCE
      LINES<-1> = T1:'</xsd:documentation>'
      LINES<-1> = '</xsd:annotation>'
      LINES<-1> = @AM
* file
      LINES<-1> = '<xsd:element name="file" type="[[FileType]]"/>'
      LINES<-1> = '<xsd:complexType name="[[FileType]]"'
      LINES<-1> = T1:'<xsd:sequence>'
      LINES<-1> = T2:'<xsd:element name="item" type="[[ItemType]]"/>'
      LINES<-1> = T1:'</xsd:sequence>'
      LINES<-1> = T1:'<xsd:attribute name="id" use="required"'
      LINES := ' type="xsd:string"/>'
      LINES<-1> = '</xsd:complexType>'
      LINES<-1> = @AM
* item
      LINES<-1> = '<xsd:complexType name="[[ItemType]]"'
* Show tags and groups
      IF TAGS NE '' OR GROUPS NE '' THEN
         LINES<-1> = T1:'<xsd:sequence>'
      END
* Show tags
      YYCNT = DCOUNT(TAGS,@AM)
      FOR YY = 1 TO YYCNT
         XX = TAGS<YY>
         LINES<-1> = T2:'<xsd:element name="'
         LINES := DICT.TITL(XX):'" type="xsd:string"/>'
      NEXT YY
* Show groups referring to them as complex types
      YYCNT = DCOUNT(GROUPS,@AM)
      FOR YY = 1 TO YYCNT
         GNAME = FIELD(GROUPS<YY>,' ',1)
         LINES<-1> = T2:'<xsd:element name="':GNAME:'" '
         LINES := 'type="':GNAME:'Type"/>'
      NEXT YY
      IF TAGS NE '' OR GROUPS NE '' THEN
         LINES<-1> = T1:'</xsd:sequence>'
      END
* id attribute
      LINES<-1> = T1:'<xsd:attribute name="id" use="required"'
      LINES := ' type="xsd:string"/>'
* Show attributes
      YYCNT = DCOUNT(ATTS,@AM)
      FOR YY = 1 TO YYCNT
         XX = ATTS<YY>
         LINES<-1> = T1:'<xsd:attribute name="':DICT.TITL(XX):'"'
         IF NOT(KILL) THEN LINES := ' use="required"'
         LINES := ' type="xsd:string"/>'
      NEXT YY
      LINES<-1> = '</xsd:complexType>'
      LINES<-1> = @AM
* Show any second level groups as complex types also
      YYCNT = DCOUNT(GROUPS,@AM)
      FOR YY = 1 TO YYCNT
         GNAME = FIELD(GROUPS<YY>,' ',2)
         IF GNAME NE '' THEN
            LINES<-1> = '<xsd:complexType name="'
            LINES := FIELD(GROUPS<YY>,' ',1):'Type">'
            LINES<-1> = T1:'<xsd:sequence>'
            LINES<-1> = T2:'<xsd:element name="':GNAME:'" '
            LINES := 'type="':GNAME:'Type"/>'
            LINES<-1> = T1:'</xsd:sequence>'
            LINES<-1> = '</xsd:complexType>'
            LINES<-1> = @AM
         END
      NEXT YY
* Loop doing each group item
      GGCNT = DCOUNT(GRPS,@AM)
      FOR GG = 1 TO GGCNT
         GNAME = GROUPS<GG>
         IF INDEX(GNAME,' ',1) THEN GNAME = FIELD(GNAME,' ',2)
         LINES<-1> = '<xsd:complexType name="':GNAME:'Type">'
         HHCNT = DCOUNT(GRPS<GG>,@VM)
         FOR HH = 1 TO HHCNT
            XX = GRPS<GG,HH>
            LINES<-1> = T1:'<xsd:element name="':DICT.TITL(XX)
            LINES := '" type="xsd:string"/>'
         NEXT HH
         LINES<-1> = '</xsd:complexType>'
         LINES<-1> = @AM
      NEXT GG
      LINES<-1> = '</xsd:schema>'
      GOSUB WRITE.LINES
      RETURN
*
OUTPUT.XSL:
***********
* Generic header
      LINES = ''
      LINES<-1> = '<?':VERSION:' standalone="yes"?>'
      LINES<-1> = '<xsl:stylesheet '
      LINES := 'xmlns:xsl="http://www.w3.org/TR/WD-xsl">'
      LINES<-1> = @AM
      LINES<-1> = '<xsl:template match="/">'
      LINES<-1> = '<html>'
      LINES<-1> = T1:'<head>'
      LINES<-1> = T2:'<style>body,h1,h2,h3 '
      LINES := '{ font-family: Tahoma,Arial,Helvetica; } '
      LINES := 'thead { text-align: left; } '
      LINES := 'tr { margin-top: 2px; } '
      LINES := 'thead { background-color: Black; color: White; }</style>'
      LINES<-1> = T2:'<title>File listing</title>'
      LINES<-1> = T1:'</head>'
      LINES<-1> = T1:'<body>'
      LINES<-1> = T2:'<xsl:apply-templates select="*" />'
      LINES<-1> = T1:'</body>'
      LINES<-1> = '</html>'
      LINES<-1> = '</xsl:template>'
      LINES<-1> = @AM; GOSUB WRITE.LINES
      LINES<-1> = '<xsl:template match="file">'
      LINES<-1> = T1:'<h1>File name:'
      LINES := '<xsl:value-of select="@id" />'
      LINES := '</h1>'
      LINES<-1> = T1:'<xsl:apply-templates select="*" />'
      LINES<-1> = '</xsl:template>'
      LINES<-1> = @AM; GOSUB WRITE.LINES

* item with id and attributes
      LINES<-1> = '<xsl:template match="item">'
      LINES<-1> = T1:'<h2>Item ID:'
      LINES := '<xsl:value-of select="@id" />'
      LINES := '</h2>'
      LINES<-1> = T1:'<div style="margin-left: 20px;">'
* Loop doing each ATT
      YYCNT = DCOUNT(ATTS,@AM)
      FOR YY = 1 TO YYCNT
         XX = ATTS<YY>
         LINE = T1:'<h3>':DICT.TITL(XX):' = "'
         LINE:= '<xsl:value-of select="@':DICT.TITL(XX):'" />'
         LINE:= '"</h3>'
         LINES<-1> = LINE
      NEXT YY
* Loop doing each TAG
      YYCNT = DCOUNT(TAGS,@AM)
      FOR YY = 1 TO YYCNT
         XX = TAGS<YY>
         LINES<-1> = T1:'<table>'
         LINES<-1> = T2:'<thead>'
         LINES<-1> = T3:'<th>':DICT.TITL(XX):'</th>'
         LINES<-1> = T2:'</thead>'
         LINES<-1> = T2:'<tbody>'
         LINES<-1> = T3:'<xsl:apply-templates select="':DICT.TITL(XX):'"/>'
         LINES<-1> = T2:'</tbody>'
         LINES<-1> = T1:'</table>'
      NEXT YY
*
* Loop doing each group (multi-level groups just have a reference)
      GGCNT = DCOUNT(GROUPS,@AM)
      FOR GG = 1 TO GGCNT
         GNAME = GROUPS<GG>
         GSUBNAME = FIELD(GNAME,' ',2)
         GNAME = FIELD(GNAME,' ',1)
         IF GSUBNAME NE '' THEN
            LINES<-1> = T1:'<xsl:apply-templates select="':GNAME:'"/>'
         END ELSE
            HHCNT = DCOUNT(GRPS<GG>,@VM)
            LINES<-1> = T1:'<h3>':GNAME:'</h3>'
            LINES<-1> = T1:'<table>'
            LINES<-1> = T2:'<thead>'
            FOR HH = 1 TO HHCNT
               XX = GRPS<GG,HH>
               LINES<-1> = T3:'<th>':DICT.TITL(XX):'</th>'
            NEXT HH
            LINES<-1> = T2:'</thead>'
            LINES<-1> = T2:'<tbody>'
            LINES<-1> = T3:'<xsl:apply-templates select="':GNAME:'"/>'
            LINES<-1> = T2:'</tbody>'
            LINES<-1> = T1:'</table>'
         END
      NEXT GG
      LINES<-1> = T1:'</div>'
      LINES<-1> = '</xsl:template>'
      LINES<-1> = @AM; GOSUB WRITE.LINES

* Loop doing each TAG
      YYCNT = DCOUNT(TAGS,@AM)
      FOR YY = 1 TO YYCNT
         XX = TAGS<YY>
         LINES<-1> = '<xsl:template match="':DICT.TITL(XX):'">'
         LINES<-1> = T1:'<xsl:for-each match=".">'
         LINES<-1> = T2:'<tr><td>'
         LINES<-1> = T2:'<xsl:value-of select="."/>'
         LINES<-1> = T2:'</td></tr>'
         LINES<-1> = T1:'</xsl:for-each>'
         LINES<-1> = '</xsl:template>'
         LINES<-1> = @AM; GOSUB WRITE.LINES
      NEXT YY

* Do each groups template
      GGCNT = DCOUNT(GROUPS,@AM)
      FOR GG = 1 TO GGCNT
         HHCNT = DCOUNT(GRPS<GG>,@VM)
         GNAME = GROUPS<GG>
         GSUBNAME = FIELD(GNAME,' ',2)
         GNAME = FIELD(GNAME,' ',1)
         LINES<-1> = '<xsl:template match="':GNAME:'">'
         IF GSUBNAME = '' THEN
            GOSUB DO.TABLE
            LINES<-1> = '</xsl:template>'
            LINES<-1> = @AM; GOSUB WRITE.LINES
         END ELSE
            LINES<-1> = T1:'<h3>':GNAME:'</h3>'
            LINES<-1> = T1:'<table>'
            LINES<-1> = T2:'<thead>'
            FOR HH = 1 TO HHCNT
               XX = GRPS<GG,HH>
               LINES<-1> = T3:'<th>':DICT.TITL(XX):'</th>'
            NEXT HH
            LINES<-1> = T2:'</thead>'
            LINES<-1> = T2:'<tbody>'
            IF GSUBNAME NE '' THEN
               LINES<-1> = T3:'<xsl:apply-templates select="*" />'
            END ELSE
               GOSUB DO.TABLE
            END
            LINES<-1> = T2:'</tbody>'
            LINES<-1> = T1:'</table>'
            LINES<-1> = '</xsl:template>'
            LINES<-1> = @AM; GOSUB WRITE.LINES
            LINES<-1> = '<xsl:template match="':GSUBNAME:'">'
            LINES<-1> = T1:'<tr>'
            FOR HH = 1 TO HHCNT
               XX = GRPS<GG,HH>
               LINES<-1> = T2:'<td><xsl:value-of select="'
               LINES := DICT.TITL(XX):'"/></td>'
            NEXT HH
            LINES<-1> = T1:'</tr>'
            LINES<-1> = '</xsl:template>'
            LINES<-1> = @AM; GOSUB WRITE.LINES
         END

      NEXT GG

      LINES<-1> = '</xsl:stylesheet>'
      GOSUB WRITE.LINES
      RETURN
*
DO.TABLE:
*********
      IF HHCNT = 1 THEN
         LINES<-1> = T1:'<xsl:for-each match=".">'
         LINES<-1> = T2:'<tr><td>'
         LINES<-1> = T2:'<xsl:value-of select="."/>'
         LINES<-1> = T2:'</td></tr>'
         LINES<-1> = T1:'</xsl:for-each>'
      END ELSE
         LINES<-1> = T1:'<tr>'
         FOR HH = 1 TO HHCNT
            XX = GRPS<GG,HH>
            LINES<-1> = T2:'<td><xsl:value-of select="'
            LINES := DICT.TITL(XX):'"/></td>'
         NEXT HH
         LINES<-1> = T1:'</tr>'
      END
      RETURN
*
WRITE.LINES:
      LLCNT = DCOUNT(LINES,@AM)
      FOR LL = 1 TO LLCNT
         LINE = LINES<LL>
         GOSUB WRITE.LINE
         IF BAD THEN STOP
      NEXT LL
      LINES = ''
      RETURN
*
WRITE.LINE:
***********
      IF FIRST THEN
         IF [[TYPE19FILE]] NE 'CRT' THEN
            WRITE LINE ON TRANSFER.FILE,[[TYPE19ITEM]]:'.':OUTPUT
         END ELSE
            CRT LINE
         END
         FIRST = @FALSE
         IF [[TYPE19FILE]] NE 'CRT' THEN
            OPENSEQ [[TYPE19FILE]],[[TYPE19ITEM]]:'.':OUTPUT TO DEST ELSE
               CRT 'Cannot OPENSEQ ':[[TYPE19ITEM]]:'.':OUTPUT
               BAD = @TRUE
               RETURN
            END
         END ELSE RETURN
* Next line for Unidata?
* RETURN
      END
      IF [[TYPE19FILE]] NE 'CRT' THEN
         WRITESEQ LINE ON DEST ELSE
* Replace above line with next on Unidata?
*        WRITESEQ LINE APPEND ON DEST ELSE
            CRT 'Cannot WRITESEQ'
            BAD = @TRUE
         END
      END ELSE CRT LINE
      RETURN
*
GRAPH:
******
*SUBROUTINE GRAPH(percent,caption)
* S[[/R]] - SKR - Graph of % completion of a task
*
      CRT @(19,11):' |---|---|---|---|---|---|---|---|---|---| ':@(-4)
      CRT @(19,12):' 0 20 40 60 80 100':@(-4)
      CRT @(19,13):' ':caption:@(-4)
      bar.length = INT(percent*40/100)
      bar = STR(' ',bar.length)
      CRT @(20,10):@(-13):bar:@(-14):percent:' %':@(-4)
      RETURN
*
CHANGE.CHARS:
*************
      FRAG = CHANGE(FRAG,'&',AMPERSAND:'amp;')
      FRAG = CHANGE(FRAG,'<',AMPERSAND:'lt;')
      FRAG = CHANGE(FRAG,'>',AMPERSAND:'gt;')
      FRAG = CHANGE(FRAG,'"',AMPERSAND:'quot;')
      FRAG = CHANGE(FRAG,"'",AMPERSAND:'039;')
      RETURN