I need to trigger a subroutine when a serial number of a product has been scanned in with a barcode scanner. The serial number looks like this: 11NNNN22334. I then need to use the scanned in serial number as a variable.
I tried dynamic regular expression hotstrings library which I include below, but I can't make it work reliably using a barcode scanner (it's too fast). I don't want to slow down the barcode scanner. It either does not trigger the subroutine at all or leaves the first digit of the serial number behind after the subroutine been triggered. Any ideas?
Test:
MsgBox, %$1% ; THIS IS THE STRING THAT TRIGGERED THE SUBROUTINE
return
hotstrings("([0-9][0-9]NNNN[0-9][0-9][0-9][0-9][0-9])", "Test")
/*
Function: HotStrings
Dynamically adds regular expression hotstrings.
Parameters:
c - regular expression hotstring
a - (optional) text to replace hotstring with or a label to goto,
leave blank to remove hotstring definition from triggering an action
Examples:
> hotstrings("(B|b)tw\s", "%$1%y the way") ; type 'btw' followed by space, tab or return
> hotstrings("i)omg", "oh my god!") ; type 'OMG' in any case, upper, lower or mixed
> hotstrings("\bcolou?r", "rgb(128, 255, 0);") ; '\b' prevents matching with anything before the word, e.g. 'multicololoured'
License:
- RegEx Dynamic Hotstrings: Modified version by Edd
- Original: <http://www.autohotkey.net/~polyethene/#hotstrings>
- Dedicated to the public domain (CC0 1.0) <http://creativecommons.org/publicdomain/zero/1.0/>
*/
hotstrings(k, a = "", Options:="")
{
static z, m = "~$", m_ = "*~$", s, t, w = 2000, sd, d = "Left,Right,Up,Down,Home,End,RButton,LButton", f = "!,+,^,#", f_="{,}"
global $
If z = ; init
{
RegRead, sd, HKCU, Control Panel\International, sDecimal
Loop, 94
{
c := Chr(A_Index + 32)
If A_Index between 33 and 58
Hotkey, %m_%%c%, __hs
else If A_Index not between 65 and 90
Hotkey, %m%%c%, __hs
}
e = 0,1,2,3,4,5,6,7,8,9,Dot,Div,Mult,Add,Sub,Enter
Loop, Parse, e, `,
Hotkey, %m%Numpad%A_LoopField%, __hs
e = BS,Shift,Space,Enter,Return,Tab,%d%
Loop, Parse, e, `,
Hotkey, %m%%A_LoopField%, __hs
z = 1
}
If (a == "" and k == "") ; poll
{
q:=RegExReplace(A_ThisHotkey, "\*\~\$(.*)", "$1")
q:=RegExReplace(q, "\~\$(.*)", "$1")
If q = BS
{
If (SubStr(s, 0) != "}")
StringTrimRight, s, s, 1
}
Else If q in %d%
s =
Else
{
If q = Shift
return
Else If q = Space
q := " "
Else If q = Tab
q := "`t"
Else If q in Enter,Return,NumpadEnter
q := "`n"
Else If (RegExMatch(q, "Numpad(.+)", n))
{
q := n1 == "Div" ? "/" : n1 == "Mult" ? "*" : n1 == "Add" ? "+" : n1 == "Sub" ? "-" : n1 == "Dot" ? sd : ""
If n1 is digit
q = %n1%
}
Else If (GetKeyState("Shift") ^ !GetKeyState("CapsLock", "T"))
StringLower, q, q
s .= q
}
Loop, Parse, t, `n ; check
{
StringSplit, x, A_LoopField, `r
If (RegExMatch(s, x1 . "$", $)) ; match
{
StringLen, l, $
StringTrimRight, s, s, l
if !(x3~="i)\bNB\b") ; if No Backspce "NB"
SendInput, {BS %l%}
If (IsLabel(x2))
Gosub, %x2%
Else
{
Transform, x0, Deref, %x2%
Loop, Parse, f_, `,
StringReplace, x0, x0, %A_LoopField%, ¥%A_LoopField%¥, All
Loop, Parse, f_, `,
StringReplace, x0, x0, ¥%A_LoopField%¥, {%A_LoopField%}, All
Loop, Parse, f, `,
StringReplace, x0, x0, %A_LoopField%, {%A_LoopField%}, All
SendInput, %x0%
}
}
}
If (StrLen(s) > w)
StringTrimLeft, s, s, w // 2
}
Else ; assert
{
StringReplace, k, k, `n, \n, All ; normalize
StringReplace, k, k, `r, \r, All
Loop, Parse, t, `n
{
l = %A_LoopField%
If (SubStr(l, 1, InStr(l, "`r") - 1) == k)
StringReplace, t, t, `n%l%
}
If a !=
t = %t%`n%k%`r%a%`r%Options%
}
Return
__hs: ; event
hotstrings("", "", Options)
Return
}
You can try to speed up the hotkeys function by fiddling with SetBatchLines:
hotstrings(k, a = "", Options:="")
{
prevBatchlines := A_BatchLines
SetBatchLines, -1
... ; rest of function here
}
; reset to whatever it was
SetBatchLines, %prevBatchlines%
Return
__hs: ; event
hotstrings("", "", Options)
Return
}
Although usually not recommended (it's nonzero by default for a reason), sometimes it is the only way.
Maybe give this a shot, it's setup to wait for your required syntax:
code:=
for k, v in StrSplit("QWERTYUIOPASDFGHJKLZXCVBNM")
Hotkey, % "~" v, WaitForBarcode
Loop 10 {
Hotkey, % "~" (10-A_Index) "", WaitForBarcode
Hotkey, % "~Numpad" (10-A_Index) "", WaitForBarcode
}
return
FoundCode(var) {
MsgBox % "Caught code: " var
}
WaitForBarcode(){
global code
k:=SubStr(A_ThisHotkey,0)
code.=k
code:=(is(SubStr(code,1,2))=1)?k:(is(SubStr(code,3,4))=2)?k:(is(SubStr(code,7,5))=1)?k:(StrLen(code)=11)?FoundCode(code):code
}
is(var) {
if var is not digit
return 1
if var is not alpha
return 2
}
I have no way of testing it with any input device other than keyboard, maybe it will work, maybe not.
Alternative for looping through keys would be:
Loop 43
Hotkey, % "~" Chr(A_Index+47), Bar
At work we have a lot of USB barcode scanners that type the scan results to the keyboard buffer.
If you have access to the barcode scanner and it's a hardware scanner,
you usually can definde a prefix/postfix code the scanner has to send before the scan. Check your scanner manual, to set it you normally just scan a few barcodes.
If you define the prefix code as a hotkey you can then run code to capture the letters until the post fix.
A simple example on key capture is
Loop {
Input, key, I L1 V
log = %log%%key%
}
#s::MsgBox, 64, Key History, %log%
It should be easy to change this to stop looping after the postfix key of your choice.
source here
Although it's not a solution, I managed to find a workaround by changing the scanner's suffix from carriage return to tab and using the original method I posted.
I am trying to process an input stream through a simple awk function, and insert the results before an anchor pattern in an existing file. My function works fine, but I can't convince awk to write the results where and how I want them. The input is being piped as XML from hunspell to my script, which accepts as an argument the output file.
hunspell -L -H ./text.xml | ./parse.awk ./output.xml
#!/usr/bin/awk -f
#
function buildObjs()
{
a["x"]=$4*mils; a["y"]=-$5*mils; a["w"]=$6*mils; a["h"]=$7*mils
print "## element" NR+1 " [x]="a["x"]" [y]="a["y"]" [width]="a["w"]" [height]="a["h"]
print "set fsize("NR+1") {FALSE}"
print "set fmargin("NR+1") {FALSE}"
print "set fmaster("NR+1") {TRUE}"
print "set ftype("NR+1") {box}"
print "set fname("NR+1") {"a["w"],a["h"]"}"
print "set fatt("NR+1") {1}"
print "set dplObjectSetup("NR+1",TRA) {"a["x"],a["y"]"}"
print "set fnum("NR+1") {}"
return 0
}
BEGIN {
FS = "[\" ]+"
mils = "0.3527"
for (i = 0; i < ARGC; i++) {
# Use this block to identify the output file we need to write to.
if (ARGV[i] ~ /output.+/) {
outFile=ARGV[i]
delete ARGV[i]
}
}
while ((getline line < outFile) > 0) {
if (line ~ !/set lineno \{.+\}/) {
print line
}
}
close(outFile)
}
{
buildObjs()
}
END {
print "set lineno {"NR+2"}"
while ((getline line < outFile) > 0) {
if (line ~ /set mode \{.+\}/) {
print line
} else
print line
}
close(outFile)
}
The anchor pattern I'm looking for in the outFile is "set lineno {3}". In the stream from text.xml, there are 20 lines, and the buildObjs function loops on every line of input, meaning I'm defining 20 objects in the output file, iterating the object count with NR+1 as I go. The number of lines to be processed will vary from job to job. As a bonus question, the anchor pattern needs to be updated so that "set lineno {3}" becomes "set lineno {NR+2}" with whatever NR+2 resolves to for that job. Presumably I will do this in the END block, but right now I just need to get my new objects into the output file.
So in summary, stream input into my awk script, process it with my function, insert the result before a pattern in an existing file, update the anchor pattern and close the modified file. This is my first substantial use of awk outside of one-liners embedded in bash scripts. Any help is most appreciated.
EDIT: I've updated the code to reflect comments from Jonathan.
EDIT2: Updated the END block. This code now does what I want.
EDIT3: Here are a few lines being piped into my script:
<w box="45.2044 92.54 61.5253 9.503" xoffset="6.27627 12.6244 19.4125 25.5994 32.2113 38.159 48.5253">Technews</w>
<w box="407.31 91.6 107.774 10.443" xoffset="1.8024 6.7944 12.1711 18.8203 29.2284 36.5576 43.2034 50.0661 56.641 61.0251 68.0434 74.6553 85.0634 92.0651 97.1957 100.274">Issue256:June27th</w>
<w box="67.923 132.463 32.747 7.337" xoffset="6.259 14.168 19.129 21.747">DALIM</w>
An excerpt from the outFile:
# file.encoding: UTF-8
# sun.jnu.encoding: UTF-8
set toolVersion {1.20}
set ftype(0) {pgs}
set fsize(0) {FALSE}
set fmargin(0) {FALSE}
set fsize(1) {TRUE}
set fmargin(1) {TRUE}
set fmaster(1) {FALSE}
set ftype(1) {pgs}
set fname(1) {}
set fatt(1) {0}
set dplObjectSetup(1,TRA) {}
set fnum(1) {}
>> New data inserted here.
set lineno {2} << This number updated to reflect the total number of objects.
set mode {1}
set preservePDF {1}
set preservePDFAction {Continue}
So, as I hope this clarifies, after processing the piped input, I'm inserting additional blocks starting with "set fsize()" and ending with "set fnum()", incrementing as I go, and summing the total number of blocks in "set lineno {}" before finally appending the trailing lines after "set lineno {}".
Here's a modified version of your executable awk script that produces the ordering you want:
#!/usr/bin/awk -f
BEGIN { FS="[{}]"; mils="0.3527"; built=1 }
FNR==NR {
if( $1 !~ /set lineno/ ) {
if( lineno != "" ) { footer[++cnt]=$0; if(cnt==3) { FS = "[\" ]+" } }
else print
}
else { lineno=$2 }
next
}
FNR!=NR && NF > 0 { built += buildObjs( built+1 ) }
END {
print "set lineno {" built "}"
for(i=1;i<=cnt;i++ ) {
print footer[i]
}
}
function buildObjs( n )
{
x=$4*mils; y=-$5*mils; w=$6*mils; h=$7*mils
print "## element" n " [x]=" x " [y]=" y " [width]=" w " [height]=" h
print "set fsize(" n ") {FALSE}"
print "set fmargin(" n ") {FALSE}"
print "set fmaster(" n ") {TRUE}"
print "set ftype(" n ") {box}"
print "set fname(" n ") {" w " " h "}"
print "set fatt(" n ") {1}"
print "set dplObjectSetup(" n ",TRA) {" x " " y "}"
print "set fnum(" n ") {}"
return 1
}
When put into a file called awko it would be run like:
hunspell -L -H ./text.xml | ./awko ./output.xml -
I don't have hunspell installed, so I tested this by running the Edit3 piped output from a file via cat:
cat ./pipeddata | ./awko ./output.xml -
Notice the - at after the output file. It's telling awk to read from stdin as the 2nd input to the awk script, which lets me deal with the first file with the standard FNR==NR { do stuff; next } logic.
Here's the breakdown:
For personal preferences, I moved the buildObjs() function to the end of the script. Notice I added a n argument to it - NR won't be used in the output. I dropped the a array because it didn't seem to be necessary and changed it's return from 0 to 1.
In the BEGIN block, setup output.xml file parsing, and mils
Whenever the FILENAME changes to -, change FS for parsing that input. The piped data FS could instead be set on the command line between the output file and the -.
When FNR==NR handle the first file
Basically, print the "header" info when your anchor hasn't been read
When the anchor is read, store it's value in lineno
After the anchor is read, store the last of the output file into the footer array in cnt order. Knowing there are only 3 lines at the end, I "cheated" to adjust the FS before the first record is read from STDIN.
When FNR!=NR and the line isn't blank (NF>0), process the piped input, incrementing built and passing it with a offset of 1 as an arg to buildObjs() ( as built starts with a value of 0 ).
In the END, the set lineno line is reconstructed/printed with the sum of lineno and built.
Then the footer from the first file is printed in order based on the cnt variable
Using the cat form, I get following:
# file.encoding: UTF-8
# sun.jnu.encoding: UTF-8
set toolVersion {1.20}
set ftype(0) {pgs}
set fsize(0) {FALSE}
set fmargin(0) {FALSE}
set fsize(1) {TRUE}
set fmargin(1) {TRUE}
set fmaster(1) {FALSE}
set ftype(1) {pgs}
set fname(1) {}
set fatt(1) {0}
set dplObjectSetup(1,TRA) {}
set fnum(1) {}
## element2 [x]=32.6389 [y]=-21.7 [width]=3.35171 [height]=0
set fsize(2) {FALSE}
set fmargin(2) {FALSE}
set fmaster(2) {TRUE}
set ftype(2) {box}
set fname(2) {3.35171 0}
set fatt(2) {1}
set dplObjectSetup(2,TRA) {32.6389 -21.7}
set fnum(2) {}
## element3 [x]=32.3073 [y]=-38.0119 [width]=3.68325 [height]=0
set fsize(3) {FALSE}
set fmargin(3) {FALSE}
set fmaster(3) {TRUE}
set ftype(3) {box}
set fname(3) {3.68325 0}
set fatt(3) {1}
set dplObjectSetup(3,TRA) {32.3073 -38.0119}
set fnum(3) {}
## element4 [x]=46.7197 [y]=-11.5499 [width]=2.58776 [height]=0
set fsize(4) {FALSE}
set fmargin(4) {FALSE}
set fmaster(4) {TRUE}
set ftype(4) {box}
set fname(4) {2.58776 0}
set fatt(4) {1}
set dplObjectSetup(4,TRA) {46.7197 -11.5499}
set fnum(4) {}
set lineno {4}
set mode {1}
set preservePDF {1}
set preservePDFAction {Continue}
Seems like your buildObj() function logic needs some attention to get things just the way you want (I suspect the indexes you've chosen need shifting).
The main action { buildObjs(); PROFIT??; } (for whatever PROFIT?? means) is acted on for each line since there is no pattern before it. You should add a pattern to recognize where you want the output added:
/set lineno \{3\}/ { buildObjs() }
{ print }
The first recognizes the pattern ({ is an awk regex metacharacter, so it must be escaped), and the second ensures that the line is printed after the output from buildObjs(). Reverse the lines if you want the set lineno {3} to come first, of course.