using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
namespace ProcessJobLog {
///
/// Read a stream file that contains i5/OS job log spool file,
/// create CSV output file of job log data.
///
class ProcessJobLog {
#region Constants for Job Log Header
///
/// The JOB_LOG_HEADER constant is used to find the
/// start of the job log header. Change this string as
/// needed for your installed national language and
/// character set.
///
private const String JOB_LOG_HEADER = "Job Log";
///
/// The JOB_NAME constant is used to find the Job name
/// string in the job log header.
///
private const String JOB_NAME = "Job name";
///
/// The JOB_USER constant is used to find the Job user
/// string in the job log header.
///
private const String JOB_USER = "User";
///
/// The JOB_NUMBER constant is used to find the Job number
/// string in the job log header.
///
private const String JOB_NUMBER = "Number";
///
/// The JOB_DESCRIPTION constant is used to find the Job
/// descrpition string in the job log header.
///
private const String JOB_DESCRIPTION = "Job description";
///
/// The JOB_DESCRIPTION_LIBRARY constant is used to find the job
/// description library in the job log header.
///
private const String JOB_DESCRIPTION_LIBRARY = "Library";
#endregion
#region Constants for Jog Log Message details
///
/// The JOB_LOG_CAUSE constant is used to find the message cause
/// in the job log.
///
private const String JOB_LOG_CAUSE = "Cause .";
///
/// The JOB_LOG_FROM_MODULE constant is used to find the from-module
/// in the job log.
///
private const String JOB_LOG_FROM_MODULE = "From module";
///
/// The JOB_LOG_FROM_PROCEDURE constant is used to find the from-procedure
/// in the job log.
///
private const String JOB_LOG_FROM_PROCEDURE = "From procedure";
///
/// The JOB_LOG_FROM_USER constant is used to find the from-user
/// in the job log.
///
private const String JOB_LOG_FROM_USER = "From user";
///
/// The JOB_LOG_MESSAGE constant is used to find the message
/// in the job log.
///
private const String JOB_LOG_MESSAGE = "Message .";
///
/// The JOB_LOG_STATEMENT constant is used to find the from-statement
/// and the to-statement in the job log.
///
private const String JOB_LOG_STATEMENT = "Statement .";
///
/// The JOB_LOG_TO_MODULE is used to find the to-module
/// in the job log.
///
private const String JOB_LOG_TO_MODULE = "To module";
///
/// The JOB_LOG_TO_PROCEDURE is used to find the to-procedure
/// in the job log.
///
private const String JOB_LOG_TO_PROCEDURE = "To procedure";
#endregion
#region Other constants
///
/// The BLANK constant is used for a single blank char.
///
private const Char BLANK = ' ';
///
/// The COLON constant is used as the keyword
/// separator for job log keywords.
///
private const String COLON = ":";
///
/// The COLON_OFFSET constant is used to indicate
/// where the value is in relation to the keyword text.
///
private const Int32 COLON_OFFSET = 4;
///
/// The QUOTE constant is used for a double-quote character.
///
private const String QUOTE = "\"";
///
/// The QUOTE_COMMA constant is used for a double-quote
/// character followed by a comma.
///
private const String QUOTE_COMMA = "\",";
#endregion
#region Main(args[])
///
/// Main method, processing starts her.
///
///
/// An array of command line parameters. May be empty.
///
static void Main(string[] args) {
// check for 2 arguments (input file, output file), display
// message, end if arguments not passed
if (args.Length < 2) {
Console.WriteLine("Usage: ProcessJobLog input_file output_file");
Console.ReadLine();
return;
}
String fileName = args[0].ToString();
String fileOut = args[1].ToString();
if (!File.Exists(fileName)) {
Console.WriteLine("{0} does not exist.", fileName);
return;
}
// Booleans used to track one-time processing of
// job log header at beginning of job log file.
bool isHeader = false;
bool isHeaderProcessed = false;
// Boolean used to track message processing
bool inMessage = false;
JobLog jobLog = new JobLog();
JobLogMessage jlm = new JobLogMessage();
String headerFields = String.Empty;
String messageFields = String.Empty;
// read/parse the input file, write out a CSV file
using (StreamWriter sw = new StreamWriter(fileOut)) {
String csvHeader = MakeCsvHeader();
sw.WriteLine(csvHeader);
Console.WriteLine(csvHeader);
using (StreamReader sr = File.OpenText(fileName)) {
String input;
headerFields = String.Empty;
while ((input = sr.ReadLine()) != null) {
// check for job log header if it has not been
// encountered yet - it should be the first
// line read in from the file
if (!isHeader) {
isHeader = CheckForHeader(input);
}
// job log header line is located, if it has
// not been processed, process the next two
// lines in the file
if ((isHeader) && !(isHeaderProcessed)) {
ProcessJobLogHeader(input, jobLog);
}
if ((inMessage) && (CheckForKeyword(input, JOB_LOG_MESSAGE))) {
jlm.Message = ExtractKeywordValue(input, JOB_LOG_MESSAGE, 999);
}
// if the line begins with a message ID, process
// the message
if (CheckForMessageId(input)) {
// if in a message, the header is done
if (!isHeaderProcessed) {
isHeaderProcessed = true;
headerFields = CreateHeaderFields(jobLog);
}
messageFields = String.Empty;
if (inMessage) {
messageFields = CreateMessageFields(jlm);
sw.WriteLine(headerFields + messageFields);
Console.WriteLine(headerFields + messageFields);
jlm = new JobLogMessage();
}
ParseMsgId(input, jlm);
inMessage = true;
}
}
}
// write out last CSV record
messageFields = CreateMessageFields(jlm);
sw.WriteLine(headerFields + messageFields);
Console.WriteLine(headerFields + messageFields);
}
Console.WriteLine("*** END ***");
Console.ReadLine();
}
#endregion
#region CheckForHeader(input)
///
/// Check for the job log header lines. The first line in the
/// job log file is the "Job Log" statement, followed
/// by two lines that contain job log header data to be parsed.
///
///
/// The input line to parse.
///
///
/// bool - true if "Display Job Log" is found.
///
private static bool CheckForHeader(String input) {
bool isHeader = false;
// look for string "Job Log" in line.
// NOTE: this is for the U.S. English OS/400 language code 2924.
// Change this text string as required.
if (input.Contains(JOB_LOG_HEADER)) {
isHeader = true;
}
return isHeader;
}
#endregion
#region CheckForKeyword
///
/// Check for a message keyword in the current line of the
/// job log being processed.
///
///
/// The line of the job log being processed.
///
///
/// The keyword to search for.
///
///
/// bool - true if the keyword is in the current line.
///
private static bool CheckForKeyword(String input, String keyword) {
if (input.Contains(keyword)) {
return true;
}
return false;
}
#endregion
#region CheckForMessageId(input)
///
/// Check for a MSGID at the start of an input line, parse as
/// a message if MSGID is found.
///
///
/// The input line to parse.
///
///
/// bool - true if MSGID is found.
///
private static bool CheckForMessageId(String input) {
bool newMsg = false;
// Use match pattern for OS/400 style message identifiers.
// Char 1 : alphabetic
// Char 2 - 3: any alphabetic or numeric
// Char 4 - 7: any hex digit (0-9, A-F)
Match match = Regex.Match(input, @"[A-Z][A-Z\d]{2}[A-F\d]{4}");
if ((match.Success) && (match.Index == 0)) {
newMsg = true;
}
return newMsg;
}
#endregion
#region CreateHeaderFields
///
/// Create the CSV header fields for the job log.
///
///
/// The JobLog object that contains the job log header values.
///
///
/// String - a comma-delimited, comma-terminated string of
/// job log header values.
///
private static string CreateHeaderFields(JobLog jobLog) {
StringBuilder sb = new StringBuilder();
sb.Append(MakeDelimitedString(jobLog.JobName));
sb.Append(MakeDelimitedString(jobLog.JobUser));
sb.Append(MakeDelimitedString(jobLog.JobNumber));
sb.Append(MakeDelimitedString(jobLog.JobDescription));
sb.Append(MakeDelimitedString(jobLog.JobDescriptionLibrary));
return sb.ToString();
}
#endregion
#region CreateMessageFields(jlm)
///
/// Create the CSV message fields for the job log.
///
///
/// The JobLogMessage object that contains the job log
/// message values.
///
///
/// String - a comma-delimited, comma-terminated string of
/// job log message values.
///
private static String CreateMessageFields(JobLogMessage jlm) {
StringBuilder sb = new StringBuilder();
sb.Append(MakeDelimitedString(jlm.MessageId));
sb.Append(MakeDelimitedString(jlm.MessageType));
sb.Append(MakeDelimitedString(jlm.MessageSeverity));
sb.Append(MakeDelimitedString(jlm.MessageDate));
sb.Append(MakeDelimitedString(jlm.MessageTime));
sb.Append(MakeDelimitedString(jlm.MessageFromProgram));
sb.Append(MakeDelimitedString(jlm.MessageFromProgramLibrary));
sb.Append(MakeDelimitedString(jlm.MessageFromInstruction));
sb.Append(MakeDelimitedString(jlm.MessageToProgram));
sb.Append(MakeDelimitedString(jlm.MessageToProgramLibrary));
sb.Append(MakeDelimitedString(jlm.MessageToInstruction));
sb.Append(MakeDelimitedString(jlm.Message));
return sb.ToString();
}
#endregion
#region ExtractKeywordValue
#region ExtractKeywordValue(input, keyword)
///
/// Extract the value for the given keyword from the input string.
/// The length to extract is 10.
///
///
/// The input string that contains the keyword and value to extract.
///
///
/// The keyword whose value is to be extracted.
///
///
/// String - the value that was extracted.
///
private static String ExtractKeywordValue(String input, String keyword) {
return ExtractKeywordValue(input, keyword, 10);
}
#endregion
#region ExtractKeywordValue(input, keyword, length)
///
/// Extract the value for the given keyword from the input string.
///
///
/// The input string that contains the keyword and value to extract.
///
///
/// The keyword whose value is to be extracted.
///
///
/// The number of characters that may be contained in the value.
///
///
/// String - the value that was extracted.
///
private static String ExtractKeywordValue(String input, String keyword, int length) {
String returnValue = String.Empty;
int start = input.IndexOf(keyword);
if (start >= 0) {
int end = input.IndexOf(COLON, start) + COLON_OFFSET;
if ((end + length) > input.Length) {
returnValue = input.Substring(end).Trim();
}
else {
returnValue = input.Substring(end, length).Trim();
}
}
return returnValue;
}
#endregion
#endregion
#region MakeCsvHeader
///
/// Create the CSV header string.
///
///
/// String - the CSV header string.
///
private static String MakeCsvHeader() {
StringBuilder sb = new StringBuilder();
// job log header fields
sb.Append("JOB,");
sb.Append("USER,");
sb.Append("NUMBER,");
sb.Append("JOBD,");
sb.Append("JOBDLIB,");
// message header fields
sb.Append("MSGID,");
sb.Append("TYPE,");
sb.Append("SEV,");
sb.Append("DATE,");
sb.Append("TIME,");
sb.Append("FROMPGM,");
sb.Append("FROMLIB,");
sb.Append("FROMINST,");
sb.Append("TOPGM,");
sb.Append("TOLIB,");
sb.Append("TOINST,");
// message fields
sb.Append("MESSAGE");
return sb.ToString();
}
#endregion
#region MakeDelimitedString(str)
///
/// Given a string, return it as a quoted comma-delimited string.
///
///
/// The string value to delimit.
///
///
/// String - the quoted comma-delimited string.
///
private static String MakeDelimitedString(String str) {
StringBuilder sb = new StringBuilder();
sb.Append(QUOTE);
sb.Append(str);
sb.Append(QUOTE_COMMA);
return sb.ToString();
}
#endregion
#region ParseMsgid(input, jlm)
///
/// Parse a line that begins with a MSGID, extract the
/// MSGID and other data fields from the line, return
/// in a string for a CSV file.
///
///
/// The input line to parse.
///
///
/// The JobLogMessage object used for the current message ID
/// being processed.
///
private static void ParseMsgId(String input, JobLogMessage jlm) {
String[] s = input.Split(new Char[] {BLANK}, StringSplitOptions.RemoveEmptyEntries);
// array s contains 11 elements from the job log MSGID line:
// [ 0]: MSGID
// [ 1]: TYPE
// [ 2]: SEV
// [ 3]: DATE
// [ 4]: TIME
// [ 5]: FROMPGM
// [ 6]: FROMLIB
// [ 7]: FROMINST
// [ 8]: TOPGM
// [ 9]: TOLIB
// [10]: TOINST
//
// On some MSGID lines, the TOLIB value is blank.
// The code in this section handles that and moves
// the final value parsed back to the TOINST element.
// process the split array, ignore the empty elements
int i = 0;
foreach(String str in s) {
str.Trim();
if (i == 0) { jlm.MessageId = str; }
if (i == 1) { jlm.MessageType = str; }
if (i == 2) { jlm.MessageSeverity = str; }
if (i == 3) { jlm.MessageDate = str; }
if (i == 4) { jlm.MessageTime = str; }
if (i == 5) { jlm.MessageFromProgram = str; }
if (i == 6) { jlm.MessageFromProgramLibrary = str; }
if (i == 7) { jlm.MessageFromInstruction = str; }
if (i == 8) { jlm.MessageToProgram = str; }
if (i == 9) {
if (str == "*N") {
jlm.MessageToProgramLibrary = BLANK.ToString();
jlm.MessageToInstruction = "*N";
}
else {
jlm.MessageToProgramLibrary = s[9];
jlm.MessageToInstruction = s[10];
}
}
i++;
}
}
#endregion
#region ProcessJobLogHeader(input, jobLog)
///
/// Process the job log header. Extract header data and
/// assign to JobLog object.
///
///
/// The current input line from the job log text file.
///
///
/// The JobLog object used in this class to contain the
/// job log data.
///
private static void ProcessJobLogHeader(String input, JobLog jobLog) {
if (jobLog.JobName.Length == 0) {
jobLog.JobName = ExtractKeywordValue(input, JOB_NAME);
}
if (jobLog.JobNumber.Length == 0) {
jobLog.JobNumber = ExtractKeywordValue(input, JOB_NUMBER);
}
if (jobLog.JobUser.Length == 0) {
jobLog.JobUser = ExtractKeywordValue(input, JOB_USER);
}
if (jobLog.JobDescription.Length == 0) {
jobLog.JobDescription = ExtractKeywordValue(input, JOB_DESCRIPTION);
}
if (jobLog.JobDescriptionLibrary.Length == 0) {
jobLog.JobDescriptionLibrary = ExtractKeywordValue(input, JOB_DESCRIPTION_LIBRARY);
}
}
#endregion
}
}