Adding loom CAD import script
This commit is contained in:
@@ -0,0 +1,805 @@
|
||||
'''
|
||||
Created on Sep 10, 2013
|
||||
|
||||
@author: Wesley Ray
|
||||
|
||||
Replacement for the XMLmapper software purchased a few years ago. The current one has too many driver issues/conflicts and
|
||||
I knew that parsing XML with Python is pretty easy.
|
||||
|
||||
This makes use of xml.etree.ElementTree to create a Document Object Model (DOM) of the input files. This makes it very easy to
|
||||
locate information if you know the the tag structure; which we do from the .ini file of the old XMLmapper program. Lucky that
|
||||
programmer decided against hard-coding that information into the binaries.
|
||||
|
||||
Taking the DOM approach is inherently slow (the entire file is loaded into memory and parsed). It's not a major problem for our
|
||||
purposes as the XML files are < 10MB, it still does take a moment to load the XML file initially. The parsing and uploading of
|
||||
the data afterwards is almost instantaneous however.
|
||||
'''
|
||||
|
||||
from STM.TabelModel import FileTableModel
|
||||
from com.ibm.as400.access import AS400JDBCDriver
|
||||
from java.awt import BorderLayout, Cursor
|
||||
from java.io import ByteArrayInputStream, File
|
||||
from java.sql import DriverManager
|
||||
from javax.imageio import ImageIO
|
||||
from javax.swing import JFrame, UIManager, JTable, JScrollPane, JButton, JPanel, \
|
||||
JOptionPane, ImageIcon
|
||||
import base64
|
||||
import datetime
|
||||
from datetime import timedelta
|
||||
import glob
|
||||
import java.lang.Exception as JavaException
|
||||
import os, getpass
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
VERSION_ID = '1.6'
|
||||
NETWORK_PATH = '\\\\EATSRV\\XML Files'
|
||||
LOG_FOLDER_DEPTH = NETWORK_PATH.count('\\') + 2;
|
||||
LOG_FILE_REGEX = '[0-9][0-9]-[0-9][0-9]-[0-9][0-9][0-9][0-9].txt'
|
||||
KEEP_LOG_FILES = 7 #Number of days to keep log files in the log folder
|
||||
DEBUG_MODE = True #Set this True to write to test database and debug log file
|
||||
|
||||
ASCII_ART = [' .d8888b. 88888888888 888b d888',
|
||||
' d88P Y88b 888 8888b d8888',
|
||||
' Y88b. 888 88888b.d88888',
|
||||
' "Y888b. 888 888Y88888P888',
|
||||
' "Y88b. 888 888 Y888P 888',
|
||||
' "888 888 888 Y8P 888',
|
||||
' Y88b d88P 888 888 " 888',
|
||||
' "Y8888P" 888 888 888']
|
||||
|
||||
'''
|
||||
Invoke QCMDEXC via a stored procedure (Thanks IBM!) so that we can get the EAT data into the system right away.
|
||||
|
||||
This runs a CL program I've written on the AS400 called EATPARSE.
|
||||
The source code for EATPARSE is located in STMLIB/QCLLESRC
|
||||
'''
|
||||
FORCE_PROCESSING = "call qsys.qcmdexc('CALL STMLIB/EATPARSE',0000000020.00000)"
|
||||
|
||||
#EATFILEA
|
||||
PATTERN_NUMBER_TAG = 'Entries/TransferObject/PseudoColorImageTO/Name' # Maps to EATDATA/EATFILEA.EATNAM
|
||||
PICKS_TAG = 'Entries/TransferObject/PseudoColorImageTO/WeftDensity' # Maps to EATDATA/EATFILEA.EATPKS
|
||||
TIME_STAMP_TAG_A = 'Entries/TransferObject/PseudoColorImageTO/LastSavedTime' # Maps to EATDATA/EATFILEA.EATTMS
|
||||
HORIZ_WARP_ENDS_TAG = 'Entries/TransferObject/PseudoColorImageTO/SizeX' # Maps to EATDATA/EATFILEA.EATHWE
|
||||
VERT_REPEAT_TAG = 'Entries/TransferObject/PseudoColorImageTO/WorldSizeY' # Maps to EATDATA/EATFILEA.EATVRP
|
||||
NOTE_TAG = 'Entries/TransferObject/PseudoColorImageTO/Note' # Maps to EATDATA/EATFILEA.EATNOT
|
||||
|
||||
#EATFILEB
|
||||
TOTAL_CARDS_TAG = 'Entries/Application/AddBoxMotion/BoxMotionInfo/BoxMotionTotalPicks' # Maps to EATDATA/EATFILEB.EATCDS
|
||||
|
||||
#EATFILEC
|
||||
#Children of BoxMotionColorList are ListEntry tags
|
||||
SEQUENCE_NUM_TAG_C = 'Entries/Application/AddBoxMotion/BoxMotionInfo/BoxMotionList/ListEntry/BoxMotionListEntry/BoxMotionColorList' # Maps to EATDATA/EATFILEC.EATSEQ
|
||||
#ListEntry Tags have these as children
|
||||
CARD_COUNT_TAG = 'BoxMotionColorListEntry/ColorCount' # Maps to EATDATA/EATFILEC.EATCCT
|
||||
|
||||
#EATFILED
|
||||
SEQUENCE_NUM_TAG_D = 'Entries/TransferObject/PseudoColorImageTO/ModificationHistoryList' # Maps to EATDATA/EATFILED.EATSQ5
|
||||
ARTIST_TAG = 'ModificationHistoryListEntry/Artist' # Maps to EATDATA/EATFILED.EATART
|
||||
COMPUTER_NAME_TAG = 'ModificationHistoryListEntry/ComputerName' # Maps to EATDATA/EATFILED.EATCNM
|
||||
TIME_STAMP_TAG_D = 'ModificationHistoryListEntry/Time' # Maps to EATDATA/EATFILED.EATTMS
|
||||
|
||||
#EATFILEE
|
||||
CAST_OUT_NAME_TAG = 'Entries/Application/MachineDefinition/MachDefCastOutName' # Maps to EATDATA/EATFILEE.EATDON
|
||||
|
||||
#EATFILEF
|
||||
SEQUENCE_NUM_TAG_F = 'Entries/Application/CalcJCSimu/SimuYarnList/WeftYarnList' # Maps to EATDATA/EATFILEF.EATSEQ
|
||||
PAT_NUM_TAG_F = 'YarnName' # Maps to EATDATA/EATFILEF.EATNAM
|
||||
|
||||
#EATFILEG
|
||||
YARN_NUM_TAG = 'Entries/Application/AddBoxMotion/BoxMotionInfo/BoxMotionYarnList/ListEntry/BoxMotionYarnListEntry/BoxMotionYarnNumber' # Maps to EATDATA/EATFILEG.EATYRN
|
||||
TISSUE_CARD_TAG = 'Entries/Application/AddBoxMotion/BoxMotionInfo/BoxMotionYarnList/ListEntry/BoxMotionYarnListEntry/BoxMotionYarnCountRegSingle' # Maps to EATDATA/EATFILEG.EATTCD
|
||||
GROUND_CARD_TAG = 'Entries/Application/AddBoxMotion/BoxMotionInfo/BoxMotionYarnList/ListEntry/BoxMotionYarnListEntry/BoxMotionYarnCountSingle' # Maps to EATDATA/EATFILEG.EATGCD
|
||||
|
||||
if(DEBUG_MODE):
|
||||
#SQL statements
|
||||
FILE_A_INSERT = 'insert into WES.EATFILEA values(?, ?, ?, ?, ?, ?, ?, ?)'
|
||||
FILE_B_INSERT = 'insert into WES.EATFILEB values(?, ?, ?)'
|
||||
FILE_C_INSERT = 'insert into WES.EATFILEC values(?, ?, ?, ?)'
|
||||
FILE_D_INSERT = 'insert into WES.EATFILED values(?, ?, ?, ?, ?, ?)'
|
||||
FILE_E_INSERT = 'insert into WES.EATFILEE values(?, ?, ?)'
|
||||
FILE_F_INSERT = 'insert into WES.EATFILEF values(?, ?, ?, ?)'
|
||||
FILE_G_INSERT = 'insert into WES.EATFILEG values(?, ?, ?, ?, ?)'
|
||||
else:
|
||||
#SQL statements
|
||||
FILE_A_INSERT = 'insert into EATDATA.EATFILEA values(?, ?, ?, ?, ?, ?, ?, ?)'
|
||||
FILE_B_INSERT = 'insert into EATDATA.EATFILEB values(?, ?, ?)'
|
||||
FILE_C_INSERT = 'insert into EATDATA.EATFILEC values(?, ?, ?, ?)'
|
||||
FILE_D_INSERT = 'insert into EATDATA.EATFILED values(?, ?, ?, ?, ?, ?)'
|
||||
FILE_E_INSERT = 'insert into EATDATA.EATFILEE values(?, ?, ?)'
|
||||
FILE_F_INSERT = 'insert into EATDATA.EATFILEF values(?, ?, ?, ?)'
|
||||
FILE_G_INSERT = 'insert into EATDATA.EATFILEG values(?, ?, ?, ?, ?)'
|
||||
|
||||
STATS_INSERT = 'insert into WES.XMLSTATS values(?, ?)'
|
||||
|
||||
#Global Vars
|
||||
transferObjectSid = 0
|
||||
applicationBoxMotionSid = 0
|
||||
applicationMachineSid = 0
|
||||
applicationCalcJCSimuSid = 0
|
||||
patternNumber = ''
|
||||
connection = None
|
||||
fileAInsert = None
|
||||
fileBInsert = None
|
||||
fileCInsert = None
|
||||
fileDInsert = None
|
||||
fileEInsert = None
|
||||
fileFInsert = None
|
||||
fileGInsert = None
|
||||
statsInsert = None
|
||||
forceUpdate = None
|
||||
setStats = None
|
||||
frame = None
|
||||
qtm = None
|
||||
logFile = None
|
||||
currentlyProcessing = ''
|
||||
|
||||
'''
|
||||
Returns a BufferedImage object of the preview image of the pattern encoded in the XML
|
||||
|
||||
Currently not used for anything, I wanted to make a nifty progress dialog that displayed a thumbnail of the pattern whose XML is currently
|
||||
being processed
|
||||
'''
|
||||
def getPreviewImage(root):
|
||||
return ImageIO.read(ByteArrayInputStream(base64.decodestring(root.find('Entries/TransferObject/PseudoColorImageTO/Preview').text)))
|
||||
|
||||
'''
|
||||
This method is one of the reasons why I love Python. In one line we are opening (or creating if the file doesn't exist) a
|
||||
text file with the current date as the name.
|
||||
|
||||
Example: 10-27-13.txt
|
||||
'''
|
||||
def openLogFile():
|
||||
if(DEBUG_MODE):
|
||||
return open(datetime.datetime.now().strftime(NETWORK_PATH + '\\logs\\%m-%d-%Y_DEBUG.txt'), 'a') # The 'a' signifies append mode, file can only be written to. Does not affect current contents of file.
|
||||
else:
|
||||
return open(datetime.datetime.now().strftime(NETWORK_PATH + '\\logs\\%m-%d-%Y.txt'), 'a')
|
||||
|
||||
'''
|
||||
This method simply prepends a time stamp to a string you wish to print to stdout.
|
||||
It also writes the resulting string out to the log file
|
||||
'''
|
||||
def debug(msg):
|
||||
now = datetime.datetime.now()
|
||||
if(now.minute < 10):
|
||||
mins = '0' + str(now.minute)
|
||||
else:
|
||||
mins = str(now.minute)
|
||||
msg = '[' + str(now.hour) + ':' + mins + '] ' + msg
|
||||
print msg
|
||||
logFile.write(msg + '\n')
|
||||
|
||||
'''
|
||||
Modified version of debug that takes a Python Error as a parameter so that it can be
|
||||
printed to the stdout and written to the log file
|
||||
'''
|
||||
def debugError(err):
|
||||
now = datetime.datetime.now()
|
||||
if(now.minute < 10):
|
||||
mins = '0' + str(now.minute)
|
||||
else:
|
||||
mins = str(now.minute)
|
||||
msg = '[' + str(now.hour) + ':' + mins + '] Error Message:'
|
||||
print msg, err
|
||||
logFile.write(msg + repr(err) + '\n')
|
||||
|
||||
'''
|
||||
Loads the IBM AS400 JDBC driver and opens a connection with the AS400
|
||||
'''
|
||||
def connect():
|
||||
DriverManager.registerDriver(AS400JDBCDriver())
|
||||
return DriverManager.getConnection('jdbc:as400://SUNBURY', 'USER', 'PASSWORD')
|
||||
|
||||
'''
|
||||
Opens a connection to the AS400, global var 'connection' gets the reference to the connection object
|
||||
pre-compiles SQL statements and assigns their references to the appropriate global variables
|
||||
'''
|
||||
def prepareToSend():
|
||||
global connection
|
||||
global fileAInsert
|
||||
global fileBInsert
|
||||
global fileCInsert
|
||||
global fileDInsert
|
||||
global fileEInsert
|
||||
global fileFInsert
|
||||
global fileGInsert
|
||||
global forceUpdate
|
||||
global statsInsert
|
||||
debug('Connecting to AS400 as user: EAT')
|
||||
connection = connect() #Open a connection
|
||||
debug('Pre-compiling SQL statements')
|
||||
fileAInsert = connection.prepareStatement(FILE_A_INSERT)
|
||||
fileBInsert = connection.prepareStatement(FILE_B_INSERT)
|
||||
fileCInsert = connection.prepareStatement(FILE_C_INSERT)
|
||||
fileDInsert = connection.prepareStatement(FILE_D_INSERT)
|
||||
fileEInsert = connection.prepareStatement(FILE_E_INSERT)
|
||||
fileFInsert = connection.prepareStatement(FILE_F_INSERT)
|
||||
fileGInsert = connection.prepareStatement(FILE_G_INSERT)
|
||||
statsInsert = connection.prepareStatement(STATS_INSERT)
|
||||
forceUpdate = connection.prepareCall(FORCE_PROCESSING)
|
||||
|
||||
'''
|
||||
This method does the actual load, parse, upload routine. Nothing profound here see the in-line comments in the method.
|
||||
|
||||
[MODIFIED] 9/13/13 - It was bugging me the whole time that if a SQLException was thrown all operations for subsequent files
|
||||
would not occur; resulting in data being in some of the files (maybe a,b,c) and not in the rest which
|
||||
would likely be an issue for the job that picks up the data on the AS400.
|
||||
So I found out that java.sql.PreparedStatement has addBatch() and executeBatch() methods. So now all
|
||||
of the actual inserting can be done after the prepared statements are created. It is the setting of the parameters
|
||||
of the prepared statements that is most likely to throw an exception. If that occurs now, none of the files
|
||||
will be updated, instead of just the files leading up to the exception being updated.
|
||||
|
||||
[MODIFIED] 9/17/13 - Wow.. I have seen the light. PreparedStatement.setObject() will do type conversion for me...
|
||||
No more type conversions, and setString, setInt, setYourFace...
|
||||
On a more serious note, this did work well and it really cleans up the code. This was easy to do partially
|
||||
because all of the data parsed from the XML files are strings. Strings can certainly be converted to
|
||||
integers and floats as long as they actually contain numbers. You would need to be careful in a language
|
||||
like Python to know what type of data is contained in a variable when passing it to setObject or you'll
|
||||
get SQLExceptions.
|
||||
'''
|
||||
def parseAndSend(inFile):
|
||||
debug('Loading ' + inFile + '...')
|
||||
tree = ET.parse(inFile)
|
||||
debug('--------------------------------')
|
||||
root = tree.getroot() #Get root XML tag
|
||||
|
||||
getSids(root) #Parse sid's into global vars
|
||||
debug('Parsing XML data for EATFILEA')
|
||||
aData = getFileAData(root) #Get data to insert into EATFILEA
|
||||
|
||||
debug('Parsing XML data for EATFILEB')
|
||||
bData = root.find(TOTAL_CARDS_TAG).text #Data for EATFILEB is just one field
|
||||
debug('\t\tTotal Cards: ' + bData)
|
||||
|
||||
debug('Parsing XML data for EATFILEC')
|
||||
cData = getFileCData(root) #Get data to insert into EATFILEC
|
||||
|
||||
debug('Parsing XML data for EATFILED')
|
||||
dData = getFileDData(root) #Get data to insert into EATFILED
|
||||
|
||||
debug('Parsing XML data for EATFILEE')
|
||||
eData = base64.decodestring(root.find(CAST_OUT_NAME_TAG).text) #Data for EATFILEE is just one field
|
||||
if 'FauxLinenElec.eatvrp' in eData:
|
||||
eData = 'G:\\EAT\\Parameter\\Cast-out\\1200.eatvrp'
|
||||
debug('Detected FauxLinenElec.eatvrp as cast out file. Writing 1200.eatvrp to database instead...')
|
||||
debug('\t\tCast Out File(Hooks): ' + eData)
|
||||
|
||||
debug('Parsing XML data for EATFILEF')
|
||||
fData = getFileFData(root) #Get data to insert into EATFILEF
|
||||
|
||||
debug('Parsing XML data for EATFILEG')
|
||||
try:
|
||||
gData = getFileGData(root) #Get data to insert into EATFILEG
|
||||
except RuntimeError, e: #Data base fields too small to accommodate numbers larger than 999, display a message for now
|
||||
debugError(e)
|
||||
JOptionPane.showMessageDialog(frame, aData[0] + '.xml has card counts over 999!\n\rYou\'ll have to key this one by hand for now :(\n\rHit okay to resume processing...', "Too Many Cards!", JOptionPane.ERROR_MESSAGE)
|
||||
return
|
||||
|
||||
debug('--------------------------------')
|
||||
|
||||
#File A
|
||||
debug('Preparing SQL statement for EATFILEA')
|
||||
fileAInsert.setObject(1, aData[0])
|
||||
fileAInsert.setObject(2, aData[1])
|
||||
fileAInsert.setObject(3, aData[0])
|
||||
fileAInsert.setObject(4, aData[2])
|
||||
fileAInsert.setObject(5, aData[3])
|
||||
fileAInsert.setObject(6, aData[4])
|
||||
fileAInsert.setObject(7, aData[5])
|
||||
fileAInsert.setObject(8, aData[6])
|
||||
|
||||
#File B
|
||||
debug('Preparing SQL statement for EATFILEB')
|
||||
fileBInsert.setObject(1, patternNumber)
|
||||
fileBInsert.setObject(2, applicationBoxMotionSid)
|
||||
fileBInsert.setObject(3, bData)
|
||||
|
||||
#File C
|
||||
debug('Preparing SQL batch for EATFILEC')
|
||||
for row in cData:
|
||||
fileCInsert.setObject(1, row[0])
|
||||
fileCInsert.setObject(2, row[1])
|
||||
fileCInsert.setObject(3, row[2])
|
||||
fileCInsert.setObject(4, row[3])
|
||||
fileCInsert.addBatch()
|
||||
|
||||
#File D
|
||||
debug('Preparing SQL batch for EATFILED')
|
||||
for row in dData:
|
||||
fileDInsert.setObject(1, row[0])
|
||||
fileDInsert.setObject(2, row[1])
|
||||
fileDInsert.setObject(3, row[2])
|
||||
fileDInsert.setObject(4, row[3])
|
||||
fileDInsert.setObject(5, row[4])
|
||||
fileDInsert.setObject(6, row[5])
|
||||
fileDInsert.addBatch()
|
||||
|
||||
#File E
|
||||
debug('Preparing SQL statement for EATFILEE')
|
||||
fileEInsert.setObject(1, patternNumber)
|
||||
fileEInsert.setObject(2, applicationMachineSid)
|
||||
fileEInsert.setObject(3, eData)
|
||||
|
||||
#File F
|
||||
debug('Preparing SQL batch for EATFILEF')
|
||||
for row in fData:
|
||||
fileFInsert.setObject(1, row[0])
|
||||
fileFInsert.setObject(2, row[1])
|
||||
fileFInsert.setObject(3, row[2])
|
||||
fileFInsert.setObject(4, row[3])
|
||||
fileFInsert.addBatch()
|
||||
|
||||
#File G
|
||||
debug('Preparing SQL batch for EATFILEG')
|
||||
for row in gData:
|
||||
fileGInsert.setObject(1, row[0])
|
||||
fileGInsert.setObject(2, row[1])
|
||||
fileGInsert.setObject(3, row[2])
|
||||
fileGInsert.setObject(4, row[3])
|
||||
fileGInsert.setObject(5, row[4])
|
||||
fileGInsert.addBatch()
|
||||
|
||||
#Do not alter stats file when testing
|
||||
if not (DEBUG_MODE):
|
||||
statsInsert.setObject(1, datetime.datetime.now().strftime('%c')[:24])
|
||||
statsInsert.setObject(2, aData[0][:7]) #The pattern number isn't really vital; if for some reason one comes through w/ more than 7 characters just slice them off
|
||||
|
||||
debug('--------------------------------')
|
||||
|
||||
debug('Inserting data into EATFILEA')
|
||||
fileAInsert.executeUpdate()
|
||||
|
||||
debug('Inserting data into EATFILEB')
|
||||
fileBInsert.executeUpdate()
|
||||
|
||||
debug('Inserting data into EATFILEC')
|
||||
fileCInsert.executeBatch()
|
||||
|
||||
debug('Inserting data into EATFILED')
|
||||
fileDInsert.executeBatch()
|
||||
|
||||
debug('Inserting data into EATFILEE')
|
||||
fileEInsert.executeUpdate()
|
||||
|
||||
debug('Inserting data into EATFILEF')
|
||||
fileFInsert.executeBatch()
|
||||
|
||||
debug('Inserting data into EATFILEG')
|
||||
fileGInsert.executeBatch()
|
||||
|
||||
#Don't touch the stats table if in debug mode
|
||||
if not (DEBUG_MODE):
|
||||
debug('Updating Statistics File')
|
||||
statsInsert.executeUpdate()
|
||||
|
||||
'''
|
||||
@param root: An elementtree.Element object referring to the root tag <EATSYSTEM3> of the XML document
|
||||
|
||||
Most of the files that are being updated on the AS400 require the Sid of the containing XML tag for some reason.
|
||||
From what I can tell the Sid exists because there are several top level tags that are all named 'Application' this
|
||||
is not a problem however because they all have a different Sid. Since we know the names of the <Application> child tags
|
||||
that contain the data we are after we can loop through the top level tags and check their children for the appropriate
|
||||
tag names. When one is found that is used a relevant variable is set to the Sid of the parent tag.
|
||||
|
||||
It seems to me like these will never change and I could have just made them constants. Might as well play it safe right?
|
||||
'''
|
||||
def getSids(root):
|
||||
global transferObjectSid
|
||||
global applicationBoxMotionSid
|
||||
global applicationMachineSid
|
||||
global applicationCalcJCSimuSid
|
||||
#Get sid values for the TransferObject tag, and the 3 Application tags that contain AddBoxMotion, MachineDefinition, and CalcJCSimu
|
||||
#ForLoopCeption
|
||||
for child in root:
|
||||
if(child.tag == 'Entries'):
|
||||
for subChild in child:
|
||||
if(subChild.tag == 'TransferObject'):
|
||||
transferObjectSid = subChild.get('sid')
|
||||
elif(subChild.tag == 'Application'):
|
||||
for subSubChild in subChild:
|
||||
if(subSubChild.tag == 'AddBoxMotion'):
|
||||
applicationBoxMotionSid = subChild.get('sid')
|
||||
elif(subSubChild.tag == 'MachineDefinition'):
|
||||
applicationMachineSid = subChild.get('sid')
|
||||
elif(subSubChild.tag == 'CalcJCSimu'):
|
||||
applicationCalcJCSimuSid = subChild.get('sid')
|
||||
|
||||
'''
|
||||
@param root: An elementtree.Element object referring to the root tag <EATSYSTEM3> of the XML document
|
||||
|
||||
This method parses the data required for EATFILEA on the AS400
|
||||
This data includes:
|
||||
Pattern Number
|
||||
Picks
|
||||
Last Saved Time
|
||||
Horizontal Warp Ends
|
||||
Vertical Repeat
|
||||
|
||||
The data is returned in a list:
|
||||
data = [patternNumber, Picks, Last_Saved_Time, Horizontal_Warp_Ends, Vertical_Repeat]
|
||||
'''
|
||||
def getFileAData(root):
|
||||
global patternNumber
|
||||
ret = []
|
||||
ret.append(base64.decodestring(root.find(PATTERN_NUMBER_TAG).text))
|
||||
ret.append(transferObjectSid)
|
||||
ret.append(root.find(PICKS_TAG).text)
|
||||
x = base64.decodestring(root.find(TIME_STAMP_TAG_A).text).split(' ')
|
||||
ret.append(x[0] + x[1])
|
||||
ret.append(root.find(HORIZ_WARP_ENDS_TAG).text)
|
||||
ret.append(root.find(VERT_REPEAT_TAG).text)
|
||||
|
||||
debug('\t\tPattern Number: ' + ret[0])
|
||||
debug('\t\tSid: ' + ret[1])
|
||||
debug('\t\tPicks: ' + ret[2])
|
||||
debug('\t\tTime Stamp: ' + ret[3])
|
||||
debug('\t\tHorizontal Warp Ends: ' + ret[4])
|
||||
debug('\t\tVertical Repeat: ' + ret[5])
|
||||
|
||||
try:
|
||||
ret.append(base64.decodestring(root.find(NOTE_TAG).text)) #This note is almost never populated, none-the-less we will try..
|
||||
except AttributeError:
|
||||
ret.append(' ')
|
||||
|
||||
debug('\t\tNote: ' + ret[6])
|
||||
patternNumber = ret[0]
|
||||
return ret
|
||||
|
||||
'''
|
||||
@param root: An elementtree.Element object referring to the root tag <EATSYSTEM3> of the XML document
|
||||
|
||||
This method parses the data required for EATFILEA on the AS400
|
||||
This data includes:
|
||||
Pattern Number
|
||||
Sid Number of the containing XML tag
|
||||
Sequence number
|
||||
Card Count, these always seem to be the same values as Ground Cards in EATFILEG
|
||||
|
||||
After looking through the programs on the AS400 that interpret the data that is sent; I've found that only
|
||||
files A, B, E, F, and G are actually used. C and D just seem to be cleared. As a result of this, I've made it
|
||||
so that if the XML tags for the data are not found in the file it is just ignored instead of halting processing.
|
||||
|
||||
02/18/14 - 6 months later; haven't run into any problems ignoring files C and D if no data is located.
|
||||
|
||||
patternNumber must have been previously determined by getFileAData()!
|
||||
The appropriate Sid number must have been previously determined by getSids()!
|
||||
|
||||
The data is returned in a 2D list:
|
||||
data = [[patternNumber, Sid, Sequence, Card_Count],
|
||||
[patternNumber, Sid, Sequence, Card_Count],
|
||||
[patternNumber, Sid, Sequence, Card_Count]]
|
||||
'''
|
||||
def getFileCData(root):
|
||||
debug('\tPattern\tSID\tSequence\tCard Count')
|
||||
ret = []
|
||||
boxMotionColorList = root.find(SEQUENCE_NUM_TAG_C)
|
||||
|
||||
try: #This data seems inconsequential to the process, if boxMotionColorList ends up None we'll return an empty list instead of halting processing
|
||||
for listEntry in boxMotionColorList:
|
||||
idno = listEntry.get('ID')
|
||||
cardCount =listEntry.find(CARD_COUNT_TAG).text
|
||||
row = [patternNumber, applicationBoxMotionSid, idno, cardCount]
|
||||
ret.append(row)
|
||||
debug('\t' + row[0] + '\t' + row[1] + '\t' + row[2] + '\t\t' + row[3])
|
||||
return ret
|
||||
except TypeError:
|
||||
debug('\tWarning: No data was located for EATFILEC. You can probably ignore this.')
|
||||
return []
|
||||
except JavaException, e:
|
||||
debug('Encountered problems while parsing data for file C. You can probably ignore this.')
|
||||
debug('Error Message: ' + e.toString())
|
||||
return []
|
||||
|
||||
|
||||
'''
|
||||
@param root: An elementtree.Element object referring to the root tag <EATSYSTEM3> of the XML document
|
||||
|
||||
This method parses the data required for EATFILED on the AS400
|
||||
This data includes:
|
||||
Pattern Number
|
||||
Sid Number of the containing XML tag
|
||||
Sequence Number
|
||||
Artist
|
||||
Computer Name
|
||||
Time Stamp
|
||||
|
||||
After looking through the programs on the AS400 that interpret the data that is sent; I've found that only
|
||||
files A, B, E, F, and G are actually used. C and D just seem to be cleared. As a result of this, I've made it
|
||||
so that if the XML tags for the data are not found in the file it is just ignored instead of halting processing.
|
||||
|
||||
02/18/14 - 6 months later; haven't run into any problems ignoring files C and D if no data is located.
|
||||
|
||||
patternNumber must have been previously determined by getFileAData()!
|
||||
The appropriate Sid number must have been previously determined by getSids()!
|
||||
|
||||
The data is returned in a 2D list:
|
||||
data = [[patternNumber, Sid, Sequence, Artist, Computer, Time_Stamp],
|
||||
[patternNumber, Sid, Sequence, Artist, Computer, Time_Stamp],
|
||||
[patternNumber, Sid, Sequence, Artist, Computer, Time_Stamp]]
|
||||
'''
|
||||
def getFileDData(root):
|
||||
debug('\tPattern\tSID\tSequence\tArtist\t\tComputer\t\t\tTime Stamp')
|
||||
ret = []
|
||||
modifcationEntries = root.find(SEQUENCE_NUM_TAG_D)
|
||||
|
||||
try: #This data seems inconsequential to the process, if modifcationEntries ends up None we'll return an empty list instead of halting processing
|
||||
for listEntry in modifcationEntries:
|
||||
idno = listEntry.get('ID')
|
||||
artist = base64.decodestring(listEntry.find(ARTIST_TAG).text)
|
||||
computer = base64.decodestring(listEntry.find(COMPUTER_NAME_TAG).text)
|
||||
timestamp = base64.decodestring(listEntry.find(TIME_STAMP_TAG_D).text).split(' ')
|
||||
timestamp = timestamp[0] + timestamp[1]
|
||||
row = [patternNumber, transferObjectSid, idno, artist, computer, timestamp]
|
||||
ret.append(row)
|
||||
debug('\t' + row[0] + '\t' + row[1] + '\t' + row[2] + '\t\t' + row[3] + '\t' + row[4] + '\t\t\t' + row[5])
|
||||
except TypeError:
|
||||
debug('\tWarning: No data was located for EATFILED. You can probably ignore this.')
|
||||
except JavaException:
|
||||
debug('\tWarning: Modification history corrupt. You can probably ignore this.')
|
||||
return ret
|
||||
|
||||
'''
|
||||
@param root: An elementtree.Element object referring to the root tag <EATSYSTEM3> of the XML document
|
||||
|
||||
This method parses the data required for EATFILEF on the AS400
|
||||
This data includes:
|
||||
Pattern Number
|
||||
Sid Number of the containing XML tag
|
||||
Yarn Sequence Number
|
||||
Yarn Type and Yarn Color in one field
|
||||
|
||||
patternNumber must have been previously determined by getFileAData()!
|
||||
The appropriate Sid number must have been previously determined by getSids()!
|
||||
|
||||
The data is returned in a 2D list:
|
||||
data = [[patternNumber, Sid, Sequence, Yarn],
|
||||
[patternNumber, Sid, Sequence, Yarn],
|
||||
[patternNumber, Sid, Sequence, Yarn]]
|
||||
'''
|
||||
def getFileFData(root):
|
||||
debug('\tPattern\tSid\tSequence\tYarn')
|
||||
ret = []
|
||||
weftYarnList = root.find(SEQUENCE_NUM_TAG_F)
|
||||
for listEntry in weftYarnList:
|
||||
idno = listEntry.get('ID')
|
||||
yarn = base64.decodestring(listEntry.find(PAT_NUM_TAG_F).text)
|
||||
row = [patternNumber, applicationCalcJCSimuSid, idno, yarn]
|
||||
ret.append(row)
|
||||
debug('\t' + row[0] + '\t' + row[1] + '\t' + row[2] + '\t\t' + row[3])
|
||||
return ret
|
||||
|
||||
'''
|
||||
@bug: Due to the inadequate size of the card fields (3 digits) we are unable to process patterns that have
|
||||
more than 999 cards for a single position.
|
||||
|
||||
@param root: An elementtree.Element object referring to the root tag <EATSYSTEM3> of the XML document
|
||||
|
||||
This method parses the data required for EATFILEG on the AS400
|
||||
This data includes:
|
||||
Pattern Number
|
||||
Sid Number of the containing XML tag
|
||||
Yarn Sequence Number
|
||||
Ground Cards
|
||||
Tissue Cards
|
||||
|
||||
patternNumber must have been previously determined by getFileAData()!
|
||||
The appropriate Sid number must have been previously determined by getSids()!
|
||||
|
||||
The data is returned in a 2D list:
|
||||
data = [[patternNumber, Sid, Sequence, Ground_Cards, Tissue_Cards],
|
||||
[patternNumber, Sid, Sequence, Ground_Cards, Tissue_Cards],
|
||||
[patternNumber, Sid, Sequence, Ground_Cards, Tissue_Cards]]
|
||||
'''
|
||||
def getFileGData(root):
|
||||
debug('\tPattern\tSid\tSequence\tGround Cards\tTissue Cards')
|
||||
ret = []
|
||||
yarnNums = root.findall(YARN_NUM_TAG)
|
||||
tissueCards = root.findall(TISSUE_CARD_TAG)
|
||||
groundCards = root.findall(GROUND_CARD_TAG)
|
||||
|
||||
for i in range(0,len(yarnNums)):
|
||||
row = [patternNumber, applicationBoxMotionSid, yarnNums[i].text, groundCards[i].text, tissueCards[i].text]
|
||||
ret.append(row)
|
||||
debug('\t' + row[0] + '\t' + row[1] + '\t' + row[2] + '\t\t' + row[3] + '\t\t' + row[4])
|
||||
if(int(row[3]) > 999) or (int(row[4]) > 999):
|
||||
raise RuntimeError('ERROR: Card counts are too large for the fields on the AS400 (> 999). You\'ll have to key this one in by hand. Sorry!')
|
||||
return ret
|
||||
|
||||
'''
|
||||
XSD files contain schema information for XML files. They can be used to validate the structure of
|
||||
an associated XML file. We do not use them for anything so this method is called during startup to
|
||||
clear them out of the folder. They are generated by the EAT software when the designers export a
|
||||
pattern in XML format.
|
||||
'''
|
||||
def deleteXSDFiles():
|
||||
for f in glob.glob(NETWORK_PATH + "\*.xsd"): #Generate a list of all XSD files in the network folder
|
||||
os.remove(f) #Delete each XSD file
|
||||
|
||||
'''
|
||||
Delete log files after KEEP_LOG_FILES days. Called on startup
|
||||
'''
|
||||
def pruneLogFiles():
|
||||
filelist = glob.glob(NETWORK_PATH + "\\logs\\" + LOG_FILE_REGEX) #Generate a list of all text files in the log folder
|
||||
then = (datetime.datetime.now() - timedelta(days=KEEP_LOG_FILES)) #logs older than this will be deleted
|
||||
for f in filelist:
|
||||
try:
|
||||
#Super dirty hack, don't care. The worst that can happen is that some logs won't get deleted
|
||||
ymd = f.split('\\')[LOG_FOLDER_DEPTH].split('.')[0].split('-')
|
||||
now = datetime.datetime(int(ymd[2]), int(ymd[0]), int(ymd[1]))
|
||||
if(now < then):
|
||||
os.remove(f)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
'''
|
||||
@param qtm:
|
||||
|
||||
Builds a list of files to be parsed and uploaded to the AS400
|
||||
|
||||
Loops through every row in the table, if the process boolean is set the full path is
|
||||
prepended to the file name and added to the list that is returned by the method.
|
||||
'''
|
||||
def getFilesSelectedForProcessing(qtm):
|
||||
ret = []
|
||||
for row in qtm.cache:
|
||||
if(row[1]):
|
||||
ret.append(NETWORK_PATH + row[0])
|
||||
return ret
|
||||
|
||||
'''
|
||||
This method is the actionPerformed property for the 'Process!' button. Everything not having to do
|
||||
with the UI happens starting from here.
|
||||
|
||||
1. The log file is opened and a dashed separator line is written to help distinguish sessions in the log file
|
||||
2. getFilesSelectedForProcessing() is called to create a list of files that were selected in the table by the user
|
||||
3. prepareToSend() is called which opens a connection with the AS400 and pre-compiles all of the SQL statements
|
||||
4. parseAndSend() is called for each file in the list. This is where most of the processing occurs.
|
||||
5. QCMDEXC is invoked via a stored procedure call in order to execute STMLIB/XMLPARSE. This causes the data uploaded to be processed by the AS400 right away.
|
||||
6. The open connection with the AS400 is closed
|
||||
7. The log file buffer is flushed in the finally block, to make sure any error information gets written to the log file.
|
||||
'''
|
||||
def process(event):
|
||||
global logFile
|
||||
global currentlyProcessing
|
||||
try:
|
||||
frame.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)) #Change mouse icon to loading icon for duration of this method
|
||||
|
||||
logFile = openLogFile()
|
||||
logFile.write('<--------------------------EAT DATA UPLOAD REQUESTED-------------------------->\n')
|
||||
debug('Sunbury Textile Mills - XMLMapper v' + VERSION_ID + ' - Author: Wesley Ray')
|
||||
|
||||
files = getFilesSelectedForProcessing(qtm)
|
||||
debug(getpass.getuser() + ' selected ' + str(len(files)) + ' file(s) for processing')
|
||||
for f in files:
|
||||
debug('\t\t' + f)
|
||||
|
||||
prepareToSend() #Open connection with server, pre-compile SQL statements
|
||||
|
||||
for inFile in files:
|
||||
currentlyProcessing = inFile.split('\\')[4]
|
||||
parseAndSend(inFile) #Parse XML data and upload to server for each file
|
||||
qtm.setValueAt(False, qtm.cache.index([currentlyProcessing, True]), 1) #Un-check successfully processed files
|
||||
debug(' ')
|
||||
|
||||
debug('Processing completed normally! AS400 connection closed.')
|
||||
JOptionPane.showMessageDialog(None, 'Processing of ' + str(len(files)) + ' file(s) completed with no errors reported!', 'Success!', JOptionPane.INFORMATION_MESSAGE)
|
||||
|
||||
except Exception, e:
|
||||
frame.setCursor(Cursor.getDefaultCursor())
|
||||
debug('An error has occurred while processing ' + currentlyProcessing)
|
||||
debug('Only the files processed successfully up to this point have been sent to the AS400!')
|
||||
debugError(e)
|
||||
JOptionPane.showMessageDialog(frame, 'An error occurred while processing ' + currentlyProcessing + '\n\rProbably a bogus XML document from the designer, have them re-send it.\n\rIf the problem persists call Wes @ Ext. 234', "An Error Has Occurred!",
|
||||
JOptionPane.ERROR_MESSAGE)
|
||||
except JavaException, e:
|
||||
frame.setCursor(Cursor.getDefaultCursor())
|
||||
debug('An error has occurred while processing ' + currentlyProcessing)
|
||||
debug('Only the files processed successfully up to this point have been sent to the AS400!')
|
||||
debug('Error Message: ' + e.toString())
|
||||
JOptionPane.showMessageDialog(frame, 'An error occurred while processing ' + currentlyProcessing + '\n\rProbably a bogus XML document from the designer, have them re-send it.\n\rIf the problem persists call Wes @ Ext. 234', "An Error Has Occurred!",
|
||||
JOptionPane.ERROR_MESSAGE)
|
||||
finally:
|
||||
fileAInsert.close() #Close PreparedStatement Objects
|
||||
fileBInsert.close()
|
||||
fileCInsert.close()
|
||||
fileDInsert.close()
|
||||
fileEInsert.close()
|
||||
fileFInsert.close()
|
||||
fileGInsert.close()
|
||||
|
||||
try:
|
||||
blah = connection.createStatement().executeQuery('select count(*) from wes.xmlstats')
|
||||
blah.next()
|
||||
blah = blah.getObject(1)
|
||||
debug(str(blah) + ' files processed since 05/15/14!')
|
||||
debug('Attempting to force data update on AS400...')
|
||||
forceUpdate.execute()
|
||||
debug('Force update successful! The printouts should be on P7 now.')
|
||||
except JavaException, e:
|
||||
debug('Force Update Error: ' + e.toString())
|
||||
JOptionPane.showMessageDialog(frame, 'Could not force update!\n\rThe data should be picked up automatically in about 5 minutes.', JOptionPane.ERROR_MESSAGE)
|
||||
|
||||
forceUpdate.close() #Close CallableStatement object
|
||||
connection.close() #Close Connection Object
|
||||
|
||||
logFile.flush()
|
||||
qtm.fireTableChanged(None) #Repaint Table
|
||||
frame.setCursor(Cursor.getDefaultCursor())
|
||||
|
||||
'''
|
||||
This method is the actionPerformed property for both the 'Select' and 'Deselect' buttons.
|
||||
|
||||
Basically loops through each row in the table data and sets the process boolean equal to
|
||||
what (event.getSource() is selectButton) evaluates to; which will be True if select all
|
||||
was clicked and false if deselect all was clicked.
|
||||
'''
|
||||
def selectAll(event):
|
||||
for row in qtm.cache:
|
||||
row[1] = (event.getSource() is selectButton) #Set 'process?' boolean for each row in the table
|
||||
qtm.fireTableChanged(None) #Repaint Table
|
||||
|
||||
'''
|
||||
I found out that Kathy will typically not delete the XML files directly after sending them, rather, she keeps them
|
||||
until the design process is finished so that if there is an issue she doesn't need to have the designers re-send
|
||||
the XML. So I got rid of the delete prompt in the process method and added a button to 'Delete Selected' files.
|
||||
|
||||
All this does is get the selected file list from the file table and delete them from the network folder.
|
||||
'''
|
||||
def deleteSelected(event):
|
||||
global logFile
|
||||
logFile = openLogFile()
|
||||
dialogResult = JOptionPane.showConfirmDialog(frame, 'Are you sure you would like to delete the selected files? This _CANNOT_ be undone!', 'Delete Files', JOptionPane.YES_NO_OPTION)
|
||||
if(dialogResult == JOptionPane.YES_OPTION):
|
||||
for f in getFilesSelectedForProcessing(qtm): #Fetch list of selected files from the file table
|
||||
os.remove(f) #Delete them
|
||||
debug(f + ' Deleted!') #Log this
|
||||
qtm.loadTable(glob.glob(NETWORK_PATH + "\*.xml")) #Refresh file table
|
||||
logFile.flush()
|
||||
logFile.close()
|
||||
|
||||
def refreshList(event):
|
||||
qtm.loadTable(glob.glob(NETWORK_PATH + "\*.xml")) #Call the loadTable method with a list of XML files in the network folder
|
||||
|
||||
if __name__ == '__main__':
|
||||
for line in ASCII_ART:
|
||||
print line
|
||||
deleteXSDFiles() #Delete all XSD files in the network folder
|
||||
pruneLogFiles() #Delete log files older than 7 days
|
||||
|
||||
frame = JFrame('Sunbury Textile Mills - XML Mapper') #Create a new JFrame()
|
||||
frame.setIconImage(ImageIcon('icon.png').getImage()) #Found an XML icon on the interwebs, better than the java logo anyways...
|
||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()) #Set look-and-feel to Windows-ish
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) #Call System.exit on frame closing
|
||||
frame.setSize(340, 340) #Good dimensions as determined by me
|
||||
frame.setLayout(BorderLayout()) #Pretty simple GUI, we'll use BorderLayout()
|
||||
|
||||
table = JTable() #Create file table
|
||||
table.setModel(FileTableModel()) #Load my AbstractTableModel implementation I created for this program
|
||||
scrollPane = JScrollPane(table) #Create a JScrollPane to put the table in
|
||||
table.setFillsViewportHeight(True) #Make table always fill the scrollPane
|
||||
table.setRowSelectionAllowed(False) #Disallow row selection, we are using in-cell JCheckBoxes instead
|
||||
qtm = table.getModel() #Get a reference to the TabelModel so we can access the table data and loadTable() method
|
||||
qtm.loadTable(glob.glob(NETWORK_PATH + "\*.xml")) #Call the loadTable method with a list of XML files in the network folder
|
||||
|
||||
processButton = JButton('Process!', actionPerformed=process) #JButton to initiate EAT data upload
|
||||
selectButton = JButton('Select All', actionPerformed=selectAll) #JButton to select all files in the list
|
||||
deselectButton = JButton('Deselect All', actionPerformed=selectAll) #JButton to deselect all selected files in the list
|
||||
refreshButton = JButton('', actionPerformed=refreshList)
|
||||
refreshButton.setIcon(ImageIcon(ImageIO.read(File('refresh.png'))))
|
||||
refreshButton.setToolTipText('Refresh file list. Useful for when designers save files to the folder after you start this program.')
|
||||
deleteButton = JButton('', actionPerformed=deleteSelected)
|
||||
deleteButton.setIcon(ImageIcon(ImageIO.read(File('delete.png'))))
|
||||
deleteButton.setToolTipText('Delete selected files')
|
||||
|
||||
topPanel = JPanel() #Since we are using BorderLayout, create a JPanel to contain all of the JButtons
|
||||
topPanel.add(selectButton)
|
||||
topPanel.add(deselectButton)
|
||||
topPanel.add(refreshButton)
|
||||
topPanel.add(deleteButton)
|
||||
|
||||
frame.add(topPanel, BorderLayout.NORTH) #Add the button JPanel to BorderLayout.NORTH
|
||||
frame.add(scrollPane, BorderLayout.CENTER) #Add the JScrollPane to BorderLayout.CENTER
|
||||
frame.add(processButton, BorderLayout.SOUTH) #Add the 'Process!' JButton to BorderLayout.SOUTH
|
||||
#frame.pack() #JTable's behave strangely with this it seems, I figured out good dimensions myself
|
||||
frame.setLocationRelativeTo(None) #Center the frame on screen
|
||||
frame.setResizable(False) #Do not allow resizing the frame
|
||||
frame.setVisible(True) #Display the frame
|
||||
@@ -0,0 +1,64 @@
|
||||
'''
|
||||
Created on Sep 11, 2013
|
||||
@author: Wesley Ray
|
||||
'''
|
||||
|
||||
from java.lang import Boolean, String
|
||||
from javax.swing.table import AbstractTableModel
|
||||
|
||||
class FileTableModel(AbstractTableModel):
|
||||
'''
|
||||
Implementation of AbstractTableModel for displaying a list of file names with a check box for each of them.
|
||||
'''
|
||||
headers = ['File', 'Process?']
|
||||
cache = []
|
||||
|
||||
#@Override
|
||||
def getColumnName(self, i):
|
||||
return self.headers[i]
|
||||
|
||||
#@Override
|
||||
def getRowCount(self):
|
||||
if (self.cache == None):
|
||||
return 0
|
||||
return len(self.cache)
|
||||
|
||||
#@Override
|
||||
def getColumnCount(self):
|
||||
return len(self.headers) #Never changes, could just be: return 2
|
||||
|
||||
#@Override
|
||||
def getValueAt(self, rowIndex, columnIndex):
|
||||
row = self.cache[rowIndex]
|
||||
return row[columnIndex]
|
||||
|
||||
#@Override
|
||||
def isCellEditable(self, rowIndex, columnIndex):
|
||||
#We have to do this to allow the state of the check boxes to be changed by the user
|
||||
if(columnIndex == 1):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
#@Override
|
||||
def setValueAt(self, aValue, rowIndex, columnIndex):
|
||||
#This toggles the state of the check box in the given rowIndex
|
||||
if(columnIndex == 1):
|
||||
self.cache[rowIndex][columnIndex] = not(self.cache[rowIndex][columnIndex])
|
||||
|
||||
#@Override
|
||||
def getColumnClass(self, column):
|
||||
if(column == 1):
|
||||
return Boolean #This causes the second column to be rendered as check boxes
|
||||
else:
|
||||
return String
|
||||
|
||||
def loadTable(self, files):
|
||||
self.cache = []
|
||||
for x in files:
|
||||
s = x.split('\\')
|
||||
row = [s[4], Boolean.FALSE]
|
||||
self.cache.append(row)
|
||||
|
||||
self.fireTableChanged(None)
|
||||
self.fireTableStructureChanged()
|
||||
Reference in New Issue
Block a user