24
Tue, Dec
1 New Articles

The CL Corner: Trim Multiple Leading Characters with TRMLFTCHR

CL
Typography
  • Smaller Small Medium Big Bigger
  • Default Helvetica Segoe Georgia Times

This command can support an extensive list of trim characters.

 

In the previous article, "Cut, Snip, Trim with TRMLFTCHR," we saw how the Trim Left Characters (TRMLFTCHR) command could allow the user to specify what leading character to trim when left-adjusting a character string value. Today, we will see what is required to have TRMLFTCHR trim any number of various leading characters from an input string of any length, left-adjust the remaining characters, and then pad the returned character string with blanks to its declared length.

 

First, we need to change the command definition to indicate that multiple TRMCHR values can be specified. The command change to enable this is shown below.

 

Cmd        Prompt('Trim Left Characters')               

Parm       Kwd(Var) Type(*Char) Len(1) RtnVal(*Yes) +   

             Min(1) Vary(*Yes *Int4) +                  

             Prompt('Decimal value')                    

Parm       Kwd(TrmChr) Type(*Char) Len(1) Dft(0) +      

             SpcVal((0)) Max(50) +                       

             Prompt('Character to trim')                

Parm       Kwd(AllTrmChr) Type(*Char) Len(1) +          

             Dft(*TrmChr) SpcVal((*TRMCHR X'FF')) +     

             Prompt('Character for all trimmed')         

 

The changes are the addition of SPCVAL((0)) and MAX(50) to the definition of the TRMCHR parameter. The default for the MAX keyword of the PARM command is 1. By changing the keyword value to 50, we define the TRMCHR parameter as supporting a list of up to 50 discrete values. The actual number of values that the user specifies will be passed to the command's CPP as a 2-byte integer value immediately followed by a contiguous list of the specified values. This is similar to how a varying-length parameter, such as VAR of TRMLFTCHR, is passed to the CPP as an integer value, indicating the declared length of the associated variable (when the variable is defined as RTNVAL(*YES)), followed immediately by the actual variable value). In the case of a list, the CPP is passed the number of elements within the array (or list) of values and then the values. There are more complex types of lists that can be defined by a command (including lists within lists), but for the TRMCHR parameter, this simple list approach suffices. The SPCVAL keyword is necessary as the TRMCHR parameter is now a list with a default value specified for the first list entry. The special value defined is the default value of 0.

 

To create the new version of the TRMLFTCHR command, you can use the same CRTCMD command that was used in previous articles:

 

CRTCMD CMD(TRMLFTCHR) PGM(TRMLFTCHR) ALLOW(*IPGM *BPGM *IMOD *BMOD)

 

As we've changed the TRMCHR parameter from being a single value to potentially a list of up to 50 values, we also need to update the TRMLFTCHR CPP. The updated source for the TRMLFTCHR program is shown below.

 

      Pgm        Parm(&Char_Parm &TrmChrParm &All_TrmChr)         

      Dcl        Var(&Char_Parm)  Type(*Char) Len(5)              

        Dcl        Var(&Char_Siz)   Type(*Int) Stg(*Defined) +    

                     DefVar(&Char_Parm 1)                         

        Dcl        Var(&First_Char) Type(*Char) Len(1) +          

                     Stg(*Defined)    DefVar(&Char_Parm 5)        

                                                                  

      Dcl        Var(&TrmChrParm) Type(*Char) Len(3)              

        Dcl        Var(&NbrTrmChr)  Type(*UInt) Len(2) +          

                     Stg(*Defined)    DefVar(&TrmChrParm 1)       

        Dcl        Var(&First_Trm)  Type(*Char) Len(1) +          

                     Stg(*Defined)    DefVar(&TrmChrParm 3)       

                                                                  

      Dcl        Var(&All_TrmChr) Type(*Char) Len(1)              

                                                                  

      Dcl        Var(&Char_Ptr)   Type(*Ptr)                      

      Dcl        Var(&Char)       Type(*Char) Len(1) +            

                   Stg(*Based)    BasPtr(&Char_Ptr)               

                                                                  

      Dcl        Var(&CharTgtPtr) Type(*Ptr)                      

      Dcl        Var(&Char_Tgt)   Type(*Char) Len(1) +            

                   Stg(*Based)    BasPtr(&CharTgtPtr)             

                                                                  

      Dcl        Var(&TrmChrPtr)  Type(*Ptr)                      

      Dcl        Var(&TrmChr)     Type(*Char) Len(1) +            

                   Stg(*Based)    BasPtr(&TrmChrPtr)              

                                                                  

      Dcl        Var(&Char_Pos)   Type(*UInt)                     

      Dcl        Var(&Char_Rem)   Type(*UInt)                     

      Dcl        Var(&Trm_Pos)    Type(*UInt)                     

      Dcl        Var(&XFF)        Type(*Char) Len(1) Value(x'FF') 

                                                                   

      ChgVar     Var(&Char_Ptr) Value(%addr(&First_Char))         

      ChgVar     Var(&CharTgtPtr) Value(%addr(&First_Char))         

                                                                    

Trim: DoFor      Var(&Char_Pos) From(1) To(&Char_Siz)               

                 ChgVar Var(&TrmChrPtr) Value(%addr(&First_Trm))    

                 DoFor Var(&Trm_Pos) From(1) To(&NbrTrmChr)         

                       If Cond(&Char *EQ &TrmChr) Then(Do)          

                          ChgVar Var(%ofs(&Char_Ptr)) +             

                                   Value(%ofs(&Char_Ptr) + 1)       

                          Iterate CmdLbl(Trim)                      

                          EndDo                                      

                       Else Cmd(Do)                                 

                            ChgVar Var(%ofs(&TrmChrPtr)) +          

                                     Value(%ofs(&TrmChrPtr) + 1)    

                            Iterate                                 

                            EndDo                                   

                       EndDo                                        

                       Leave                                        

                 EndDo                                               

                                                                     

      If         Cond(&Char_Pos *LE &Char_Siz) Then(Do)              

                 DoFor Var(&Char_Pos) From(&Char_Pos) To(&Char_Siz)  

                       ChgVar Var(&Char_Tgt) Value(&Char)            

                       ChgVar Var(%ofs(&CharTgtPtr)) +               

                                Value(%ofs(&CharTgtPtr) + 1)         

                       ChgVar Var(%ofs(&Char_Ptr)) +                 

                                Value(%ofs(&Char_Ptr) + 1)           

                       EndDo                                         

                                                                      

                 If    Cond(&Char_Ptr *NE &CharTgtPtr) Then(Do)      

                       ChgVar Var(&Char_Rem) Value( +                

                                (%ofs(&Char_Ptr) - %ofs(&CharTgtPtr)))

                       DoFor Var(&Char_Pos) From(1) To(&Char_Rem)    

                             ChgVar Var(&Char_Tgt) Value(' ')        

                             ChgVar Var(%ofs(&CharTgtPtr)) +         

                                      Value(%ofs(&CharTgtPtr) + 1)   

                             EndDo                                   

                       EndDo                                         

                 EndDo                                               

                                                                      

      Else       Cmd(Do)                                             

                 If Cond(&All_TrmChr *EQ &XFF) Then(Do)              

                    ChgVar Var(&TrmChrPtr) Value(%addr(&First_Trm))  

                    ChgVar Var(&Char_Tgt) Value(&TrmChr)             

                    EndDo                                            

                 Else Cmd( +                                         

                      ChgVar Var(&Char_Tgt) Value(&All_TrmChr))       

                 ChgVar Var(%ofs(&CharTgtPtr)) +                     

                          Value(%ofs(&CharTgtPtr) + 1)               

                 DoFor Var(&Char_Pos) From(2) To(&Char_Siz)          

                       ChgVar Var(&Char_Tgt) Value(' ')              

                       ChgVar Var(%ofs(&CharTgtPtr)) +               

                                Value(%ofs(&CharTgtPtr) + 1)   

                       EndDo                                   

                 EndDo                                         

                                                               

      EndPgm                                                               

 

Reviewing the updated program shown above, the following changes have been made:

 

  • The name of the second parameter passed to the TRMLFTCHR CPP is changed to &TrmChrParm.

 

                Parm(&Char_Parm &TrmChrParm &All_TrmChr)  

 

This change is made solely for consistency with earlier versions of the CPP. As you will see shortly, this change allows us to continue to use the variable name &TrmChr to represent the trim character being tested.

 

  • The parameter &TrmChrParm is declared with this definition:

 

        Dcl        Var(&TrmChrParm) Type(*Char) Len(3)            

        Dcl        Var(&NbrTrmChr)  Type(*UInt) Len(2) +        

                     Stg(*Defined)    DefVar(&TrmChrParm 1)     

        Dcl        Var(&First_Trm)  Type(*Char) Len(1) +        

                     Stg(*Defined)    DefVar(&TrmChrParm 3)     

 

Using the same style previously used to define the parameter &Char_Parm, we use defined storage to redefine &TrmChrParm as a 2-byte unsigned integer (&NbrTrmChr) representing the number of trim characters specified, and a 1-byte character variable (&First_Trm) representing the first trim character in the TRMCHR list specified by the user.

 

  • Again following the style previously used to support the varying-length parameter &Char_Parm, we define a pointer to address the trim characters (&TrmChrPtr), a 1-byte character variable (&TrmChr) based on the &TrmChrPtr pointer, and an unsigned integer (&Trm_Pos) to serve as a DOFOR control variable when looping through the user-specified trim character list.

 

      Dcl        Var(&TrmChrPtr)  Type(*Ptr)          

      Dcl        Var(&TrmChr)     Type(*Char) Len(1) +

                   Stg(*Based)    BasPtr(&TrmChrPtr)  

                                                

                                                

      Dcl        Var(&Trm_Pos)    Type(*UInt)         

 

  • Associate the command label "Trim:" to the initial DOFOR loop of the CPP:

 

                Trim:   DoFor      Var(&Char_Pos) From(1) To(&Char_Siz)  

 

This label is used as the target of an ITERATE command used to control the flow of the program when searching for leading trim characters.

 

  • Replace the previous test for a single trim character…

 

      If Cond(&Char *EQ &TrmChr) Then(Do)                      

         ChgVar Var(%ofs(&Char_Ptr)) Value(%ofs(&Char_Ptr) + 1)

         Iterate                                               

         EndDo                   

      Else Cmd(Leave)                                

 

                …with the following:

 

      ChgVar Var(&TrmChrPtr) Value(%addr(&First_Trm))  

      DoFor Var(&Trm_Pos) From(1) To(&NbrTrmChr)       

            If Cond(&Char *EQ &TrmChr) Then(Do)        

               ChgVar Var(%ofs(&Char_Ptr)) +           

                        Value(%ofs(&Char_Ptr) + 1)     

               Iterate CmdLbl(Trim)                    

               EndDo                                   

            Else Cmd(Do)                               

                 ChgVar Var(%ofs(&TrmChrPtr)) +          

                          Value(%ofs(&TrmChrPtr) + 1)    

                 Iterate                                 

                 EndDo                                   

            EndDo                                      

            Leave

 

As the program is now checking for more than one trim character, the program must, prior to testing the list of trim characters, reset the pointer variable &TrmChrPtr. This ensures that the program is consistently starting from the beginning of the list for each comparison to the current character. This "reset" is done by setting &TrmChrPtr to the address of &First_Trm. The program then enters a DOFOR loop, controlled by the number of trim characters specified (&NbrTrmChr), and tests the current character of the input character string (&Char) to the trim character currently being tested for (&TrmChr).

 

If they are equal, the program trims the current character by moving to the next character in the input character string and restarting the initial DOFOR loop. This is done by incrementing the pointer variable &Char_Ptr by one and using the command ITERATE CMDLBL(TRIM) to pass control back to the DOFOR loop identified by the label Trim.

 

If &Char and &TrmChr are not equal, the program tests the next trim character by incrementing pointer variable &TrmChrPtr by one and using the command ITERATE, with no CMDLBL keyword, to pass control back to the innermost DOFOR loop (the one associated with testing each specified trim character).

 

The only time this inner DOFOR loop is exited is when &Char is not equal to &TrmChr and all specified trim characters have been tested. In this situation, the program LEAVEs the DOFOR loop associated with the label "Trim," left-adjusts all characters from the current position to the end of the input character string, and blank-pads the resulting string. The logic associated with the left-adjusting and blank-padding of the character string is unchanged from the previous version of the CPP.

 

  • Replace the logic associated with the *TRMCHR special value of keyword ALLTRMCHR…

 

      If Cond(&All_TrmChr *EQ &XFF) Then( +     

         ChgVar Var(&Char_Tgt) Value(&TrmChr))  

 

                …with

 

      If Cond(&All_TrmChr *EQ &XFF) Then(Do)            

         ChgVar Var(&TrmChrPtr) Value(%addr(&First_Trm))

         ChgVar Var(&Char_Tgt) Value(&TrmChr)           

         EndDo                                          

 

With this change, the use of special value *TRMCHR for the ALLTRMCHR keyword now indicates that the first trim character specified by the TRMCHR parameter list is to be used when no significant characters are found in the input character string.

 

To compile the new CPP, use either of the following commands:

 

CRTBNDCL PGM(TRMLFTCHR)

 

or

 

CRTCLPGM PGM(TRMLFTCHR)

 

As mentioned in the earlier article, "Going Where No Substring (%SST) Operation Can Go," if you are using the CRTBNDCL command to create an ILE program, make sure that you have the following PTFs applied to your system prior to compiling the CPP:

 

  • V5R4 SI39398
  • V6R1 SI39405
  • V7R1 SI39407

 

To test the latest version of the TRMLFTCHR command, you can use the following CL program:

 

Pgm                                            

Dcl        Var(&Char10)   Type(*Char) Len(10)  

Dcl        Var(&Char50)   Type(*Char) Len(50)  

                                               

ChgVar     Var(&Char10) Value(12.34)           

SndPgmMsg  Msg('Originally' *BCat &Char10)     

TrmLftChr  Var(&Char10)                        

SndPgmMsg  Msg('Now.......' *BCat &Char10)     

                                               

ChgVar     Var(&Char10) Value(0)               

SndPgmMsg  Msg('Originally' *BCat &Char10)     

TrmLftChr  Var(&Char10) AllTrmChr('?')         

SndPgmMsg  Msg('Now.......' *BCat &Char10)     

                                                

ChgVar     Var(&Char10) Value(0)               

SndPgmMsg  Msg('Originally' *BCat &Char10)     

TrmLftChr  Var(&Char10)                       

SndPgmMsg  Msg('Now.......' *BCat &Char10)    

                                               

ChgVar     Var(&Char10) Value('***ABC EF')    

SndPgmMsg  Msg('Originally' *BCat &Char10)    

TrmLftChr  Var(&Char10)                       

SndPgmMsg  Msg('Now.......' *BCat &Char10)    

                                               

ChgVar     Var(&Char10) Value('***ABC EF')    

SndPgmMsg  Msg('Originally' *BCat &Char10)    

TrmLftChr  Var(&Char10) TrmChr(*)             

SndPgmMsg  Msg('Now.......' *BCat &Char10)    

                                               

ChgVar     Var(&Char50) Value('   ABCDEF')    

SndPgmMsg  Msg('Originally' *BCat &Char50)    

TrmLftChr  Var(&Char50) TrmChr(' ')           

SndPgmMsg  Msg('Now.......' *BCat &Char50)    

                                                

ChgVar     Var(&Char10) Value('     *1.23')    

SndPgmMsg  Msg('Originally' *BCat &Char10)     

TrmLftChr  Var(&Char10) TrmChr(* ' ')          

SndPgmMsg  Msg('Now.......' *BCat &Char10)     

                                                

ChgVar     Var(&Char10) Value('* * * 1.23')    

SndPgmMsg  Msg('Originally' *BCat &Char10)     

TrmLftChr  Var(&Char10) TrmChr(* ' ')          

SndPgmMsg  Msg('Now.......' *BCat &Char10)     

                                                

ChgVar     Var(&Char50) Value(' * * *1.23')    

SndPgmMsg  Msg('Originally' *BCat &Char50)     

TrmLftChr  Var(&Char50) TrmChr(* ' ')          

SndPgmMsg  Msg('Now.......' *BCat &Char50)     

                                               

EndPgm                                         

 

This sample program provides for both regression testing, in that the first several tests are the same we used in the previous article, and also new function testing with the addition of the last few test cases. Running the test program will result in these messages being displayed:

 

Originally 0000012.34         

Now....... 12.34              

Originally 0000000000         

Now....... ?                  

Originally 0000000000         

Now....... 0                   

Originally ***ABC EF             

Now....... ***ABC EF             

Originally ***ABC EF             

Now....... ABC EF                

Originally    ABCDEF             

Now....... ABCDEF                

Originally      *1.23             

Now....... 1.23                  

Originally * * * 1.23            

Now....... 1.23                  

Originally  * * *1.23            

Now....... 1.23                  

 

In developing the TRMLFTCHR command, you have seen how to create user commands incorporating several features. These features included returning a value to a CL variable, supporting varying-length character variables, defining default values for parameters, supporting a list of values for a parameter, and mapping special values to a replacement value. You may also have picked up a few CL programming techniques related to defined storage, based storage, and pointers. With these latest changes, we've pretty much exhausted what a user might want to do in terms of left-adjusting the value of a character variable.

 

Returning briefly to the original scenario in the article "Create Reusable Code," where we wanted to left-adjust and blank-pad a decimal value, a user command could also have been created to eliminate the need for using CHGVAR to change the TYPE(*DEC) numeric variable &Number to the TYPE(*CHAR) character variable &Char prior to using the TRMLFTCHR command. A command such as Edit Decimal Value (EDTDECVXCL), provided in the no-charge base of the eXtreme CL product, could have been implemented. The EDTDECVXCL command accepts a TYPE(*DEC) CL variable of any (CL-supported) digit and decimal position length, applies either an edit code or an edit word to the decimal value, and then returns the edited value as either a left-adjusted or right-adjusted character variable of any length. In other words, rather than coding…

 

Pgm                                                          

Dcl        Var(&Number)   Type(*Dec)  Len(6 2)  Value(1.23)  

Dcl        Var(&Char)     Type(*Char) Len(10)                

                                                             

ChgVar     Var(&Char) Value(&Number)                         

TrmLftChr  Var(&Char)                                        

                                                              

SndPgmMsg  Msg('The cost is $' *Cat &Char)                   

                                                             

EndPgm                                                       

 

…we could have used an implementation such as this:

 

Pgm                                                           

Dcl        Var(&Number)   Type(*Dec)  Len(6 2)  Value(1.23)   

Dcl        Var(&Char)     Type(*Char) Len(10)                 

                                                               

EdtDecVXCL Var(&Char) Value(&Number) EdtCde(3) Align(*Left)   

                                                              

SndPgmMsg  Msg('The cost is $' *Cat &Char)                    

                                                               

EndPgm                                                        

 

Both programs would display the same message:

 

The cost is $1.23

 

I elected to use the TRMLFTCHR approach in this series as I believe it provides for a more gradual learning curve when discussing the creation of user commands. User-created commands, however, can support many more features than those we've discussed, such as decimal variables of varying digits and decimal position as shown above. I encourage you to prompt the PARM command and review everything that can be done with a command parameter. Commands provide you with a very easy way to productively share common logic across your application programs. In this series of articles, we've only scratched the surface of what you can do!

 

One item, however, that is lacking, though not tied directly to the PARM command, is any online help for the TRMLFTCHR command. In the next article, we'll see how we can add F1 (Help) support to TRMLFTCHR.

More CL Questions?

Wondering how to accomplish a function in CL? Send your CL-related questions to me at This email address is being protected from spambots. You need JavaScript enabled to view it.. I'll try to answer your burning questions in future columns.

BLOG COMMENTS POWERED BY DISQUS

LATEST COMMENTS

Support MC Press Online

$

Book Reviews

Resource Center

  • SB Profound WC 5536 Have you been wondering about Node.js? Our free Node.js Webinar Series takes you from total beginner to creating a fully-functional IBM i Node.js business application. You can find Part 1 here. In Part 2 of our free Node.js Webinar Series, Brian May teaches you the different tooling options available for writing code, debugging, and using Git for version control. Brian will briefly discuss the different tools available, and demonstrate his preferred setup for Node development on IBM i or any platform. Attend this webinar to learn:

  • SB Profound WP 5539More than ever, there is a demand for IT to deliver innovation. Your IBM i has been an essential part of your business operations for years. However, your organization may struggle to maintain the current system and implement new projects. The thousands of customers we've worked with and surveyed state that expectations regarding the digital footprint and vision of the company are not aligned with the current IT environment.

  • SB HelpSystems ROBOT Generic IBM announced the E1080 servers using the latest Power10 processor in September 2021. The most powerful processor from IBM to date, Power10 is designed to handle the demands of doing business in today’s high-tech atmosphere, including running cloud applications, supporting big data, and managing AI workloads. But what does Power10 mean for your data center? In this recorded webinar, IBMers Dan Sundt and Dylan Boday join IBM Power Champion Tom Huntington for a discussion on why Power10 technology is the right strategic investment if you run IBM i, AIX, or Linux. In this action-packed hour, Tom will share trends from the IBM i and AIX user communities while Dan and Dylan dive into the tech specs for key hardware, including:

  • Magic MarkTRY the one package that solves all your document design and printing challenges on all your platforms. Produce bar code labels, electronic forms, ad hoc reports, and RFID tags – without programming! MarkMagic is the only document design and print solution that combines report writing, WYSIWYG label and forms design, and conditional printing in one integrated product. Make sure your data survives when catastrophe hits. Request your trial now!  Request Now.

  • SB HelpSystems ROBOT GenericForms of ransomware has been around for over 30 years, and with more and more organizations suffering attacks each year, it continues to endure. What has made ransomware such a durable threat and what is the best way to combat it? In order to prevent ransomware, organizations must first understand how it works.

  • SB HelpSystems ROBOT GenericIT security is a top priority for businesses around the world, but most IBM i pros don’t know where to begin—and most cybersecurity experts don’t know IBM i. In this session, Robin Tatam explores the business impact of lax IBM i security, the top vulnerabilities putting IBM i at risk, and the steps you can take to protect your organization. If you’re looking to avoid unexpected downtime or corrupted data, you don’t want to miss this session.

  • SB HelpSystems ROBOT GenericCan you trust all of your users all of the time? A typical end user receives 16 malicious emails each month, but only 17 percent of these phishing campaigns are reported to IT. Once an attack is underway, most organizations won’t discover the breach until six months later. A staggering amount of damage can occur in that time. Despite these risks, 93 percent of organizations are leaving their IBM i systems vulnerable to cybercrime. In this on-demand webinar, IBM i security experts Robin Tatam and Sandi Moore will reveal:

  • FORTRA Disaster protection is vital to every business. Yet, it often consists of patched together procedures that are prone to error. From automatic backups to data encryption to media management, Robot automates the routine (yet often complex) tasks of iSeries backup and recovery, saving you time and money and making the process safer and more reliable. Automate your backups with the Robot Backup and Recovery Solution. Key features include:

  • FORTRAManaging messages on your IBM i can be more than a full-time job if you have to do it manually. Messages need a response and resources must be monitored—often over multiple systems and across platforms. How can you be sure you won’t miss important system events? Automate your message center with the Robot Message Management Solution. Key features include:

  • FORTRAThe thought of printing, distributing, and storing iSeries reports manually may reduce you to tears. Paper and labor costs associated with report generation can spiral out of control. Mountains of paper threaten to swamp your files. Robot automates report bursting, distribution, bundling, and archiving, and offers secure, selective online report viewing. Manage your reports with the Robot Report Management Solution. Key features include:

  • FORTRAFor over 30 years, Robot has been a leader in systems management for IBM i. With batch job creation and scheduling at its core, the Robot Job Scheduling Solution reduces the opportunity for human error and helps you maintain service levels, automating even the biggest, most complex runbooks. Manage your job schedule with the Robot Job Scheduling Solution. Key features include:

  • LANSA Business users want new applications now. Market and regulatory pressures require faster application updates and delivery into production. Your IBM i developers may be approaching retirement, and you see no sure way to fill their positions with experienced developers. In addition, you may be caught between maintaining your existing applications and the uncertainty of moving to something new.

  • LANSAWhen it comes to creating your business applications, there are hundreds of coding platforms and programming languages to choose from. These options range from very complex traditional programming languages to Low-Code platforms where sometimes no traditional coding experience is needed. Download our whitepaper, The Power of Writing Code in a Low-Code Solution, and:

  • LANSASupply Chain is becoming increasingly complex and unpredictable. From raw materials for manufacturing to food supply chains, the journey from source to production to delivery to consumers is marred with inefficiencies, manual processes, shortages, recalls, counterfeits, and scandals. In this webinar, we discuss how:

  • The MC Resource Centers bring you the widest selection of white papers, trial software, and on-demand webcasts for you to choose from. >> Review the list of White Papers, Trial Software or On-Demand Webcast at the MC Press Resource Center. >> Add the items to yru Cart and complet he checkout process and submit

  • Profound Logic Have you been wondering about Node.js? Our free Node.js Webinar Series takes you from total beginner to creating a fully-functional IBM i Node.js business application.

  • SB Profound WC 5536Join us for this hour-long webcast that will explore:

  • Fortra IT managers hoping to find new IBM i talent are discovering that the pool of experienced RPG programmers and operators or administrators with intimate knowledge of the operating system and the applications that run on it is small. This begs the question: How will you manage the platform that supports such a big part of your business? This guide offers strategies and software suggestions to help you plan IT staffing and resources and smooth the transition after your AS/400 talent retires. Read on to learn: