I need to concatenate several strings - codesys

I am new to stuctured text and I would like to know how to concatenate several string. The cleanest way possible. I this instance I only need to change one variable when creating the string. I have another where I need to do 2. That number will probably grow. The purpose of this is so I can send XML message to an HTTP server. This is for logging data.
In this instance it is the reader variable which is a word.
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://tempuri.org/IService/receiveHeartbeat</Action>
</s:Header>
<s:Body>
<receiveHeartbeat xmlns="http://tempuri.org/">
<reader>**Word Variable**</reader>
</receiveHeartbeat>
</s:Body>
</s:Envelope>

You can chain CONCAT functions like so:
concat3: STRING := CONCAT(CONCAT(str1, str2), str3);
However, beware that by default STRING is only 80 characters (bytes) long. You can specify the size using parenthesis:
concat3: STRING(255) := CONCAT(CONCAT(str1, str2), str3);
But again, the standard CONCAT function only accepts and returns strings of up to 255 in length!
If you need strings longer than 255, then check Working with Strings More Than 255 Characters in the codesys documentation

If you are using Wago then you should have access to their CONCAT functions...CONCAT3(),CONCAT4()...CONCAT9(). This is much cleaner than nesting a lot of the standard CONCAT funct

I take it that you need to do this in JavaScript.
var replaceDue = "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">\
<s:Header>\
<Action s:mustUnderstand=\"1\"xmlns=\"http://schemas.microsoft.com/ws/2005/05/addressing/none\">http://tempuri.org/IService/receiveHeartbeat</Action>\
</s:Header>\
<s:Body>\
<receiveHeartbeat xmlns=\"http://tempuri.org/\">\
<reader>**Word Variable**</reader>\
</receiveHeartbeat>\
</s:Body>\
</s:Envelope>";
var wordVariable = "value to replace";
var replaceDone = replaceDue.replace("**Word Variable**", wordVariable);

I think I have found a solution. I don't like it though. It's not very clean.
Reader_ID: STRING := '0';
msg: STRING(500);
Msg1: STRING(250) := '<s:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"><s:Header>';
Msg2: STRING(250) := '<Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">http://tempuri.org/IService/receiveHeartbeat</Action>';
Msg3: STRING := '</s:Header><s:Body><receiveHeartbeat xmlns="http://tempuri.org/"><reader>';
MsgAfter: STRING := '</reader></receiveHeartbeat></s:Body></s:Envelope>';
msg := CONCAT(Msg1,Msg2);
msg := CONCAT(msg,Msg3);
msg := CONCAT(msg,Reader_ID);
msg := CONCAT(msg,MsgAfter);
It seems that string sizes are limited to 500 characters. Since the point of this is to create an XML message to send via HTTP. What happens when my messages inevitably get larger than 500 character. I am using the WagoLibHttp_02 library for http.

Related

How to make a multiline string value from MongoDB's bson.M in Golang?

I'm creating a telegram bot using Golang, and I need some advice on how to retrieve a multiline string value from function in Golang, that has same logic like this Python string
answer = """1 John 95
2 Sam 89
3 Mike 72"""
I have a function that creates a MongoDB request that gets me a data in bson.M datatype. And after that I need to send this queried data to a user as a single string value using this form:
msg := tgbotapi.NewMessage(
update.Message.Chat_ID,
answer,
)
bot.Send(msg)
I don't really know how to transform this bson.M data into a single multiline string.
bson.M response that I get from the function:
[map[_id:ObjectID("62a4acf2a494a2814238c6e1") bandMember:John name:School12 points:95]
map[_id:ObjectID("62a4acf2a494a2814238c6e2") bandMember:Sam name:School15 points:89]
map[_id:ObjectID("62a4acf2a494a2814238c6e3") bandMember:Mike name:School7 points:72]]
And I have to insert it in a string variable of "answer" (see above example)
Thanks in advance!
Loop through the documents and sprintf a line for each document. Join the lines with newline to get the final result.
var lines []string
for i, m := range data {
lines = append(lines, fmt.Sprintf("%d %s %d", i+1, m["bandMember"], m["points"]))
}
result := strings.Join(lines, "\n")

Converting numbers into timestamps (inserting colons at specific places)

I'm using AutoHotkey for this as the code is the most understandable to me. So I have a document with numbers and text, for example like this
120344 text text text
234000 text text
and the desired output is
12:03:44 text text text
23:40:00 text text
I'm sure StrReplace can be used to insert the colons in, but I'm not sure how to specify the position of the colons or ask AHK to 'find' specific strings of 6 digit numbers. Before, I would have highlighted the text I want to apply StrReplace to and then press a hotkey, but I was wondering if there is a more efficient way to do this that doesn't need my interaction. Even just pointing to the relevant functions I would need to look into to do this would be helpful! Thanks so much, I'm still very new to programming.
hfontanez's answer was very helpful in figuring out that for this problem, I had to use a loop and substring function. I'm sure there are much less messy ways to write this code, but this is the final version of what worked for my purposes:
Loop, read, C:\[location of input file]
{
{ If A_LoopReadLine = ;
Continue ; this part is to ignore the blank lines in the file
}
{
one := A_LoopReadLine
x := SubStr(one, 1, 2)
y := SubStr(one, 3, 2)
z := SubStr(one, 5)
two := x . ":" . y . ":" . z
FileAppend, %two%`r`n, C:\[location of output file]
}
}
return
Assuming that the "timestamp" component is always 6 characters long and always at the beginning of the string, this solution should work just fine.
String test = "012345 test test test";
test = test.substring(0, 2) + ":" + test.substring(2, 4) + ":" + test.substring(4, test.length());
This outputs 01:23:45 test test test
Why? Because you are temporarily creating a String object that it's two characters long and then you insert the colon before taking the next pair. Lastly, you append the rest of the String and assign it to whichever String variable you want. Remember, the substring method doesn't modify the String object you are calling the method on. This method returns a "new" String object. Therefore, the variable test is unmodified until the assignment operation kicks in at the end.
Alternatively, you can use a StringBuilder and append each component like this:
StringBuilder sbuff = new StringBuilder();
sbuff.append(test.substring(0,2));
sbuff.append(":");
sbuff.append(test.substring(2,4));
sbuff.append(":");
sbuff.append(test.substring(4,test.length()));
test = sbuff.toString();
You could also use a "fancy" loop to do this, but I think for something this simple, looping is just overkill. Oh, I almost forgot, this should work with both of your test strings because after the last colon insert, the code takes the substring from index position 4 all the way to the end of the string indiscriminately.

Is white space relevant when casting from the 'any' type in Apama EPL?

I am on Apama 10.3 (Community Edition):
any emptyString := "";
any emptyDictionary := new dictionary<string,any>;
string myString := <string> emptyString;
dictionary<string,any> := <dictionary<string,any>> emptyDictionary;
The cast in line 3 works, but in line 4 Designer complains about unexpected token: <. Only if I use white spaces does it work:
dictionary<string,any> := <dictionary< string,any> > emptyDictionary;
In the documentation Developing Apama Applications this is not mentioned but on page 296 when casting with optional<>, the correct syntax with the white spaces is used.
Does this work as expected or is it a bug?
The problem here isn't about casting to an any type. This is due to the EPL parser always interpreting expression >> as a right-shift operator. If you need to close two angle brackets, you always need to use a space between them. It’s only the closing brackets that are affected (as you’d never need to write << in EPL).
The form I always use is:
dictionary<string,any> x := <dictionary<string,any> > emptyDictionary;
sequence<sequence<string> > jaggedArray := new sequence<sequence<string> >;

Mask address in Crystal Reports

I am creating a report by Crystal Reports which will mask address field depending upon parameter set to the report. I know this can be done using formula. I can successfully mask with hard coded value. However I need something like password masking. For example, if address is D/302 ABC apartment, it should be displayed as X/XXX XXX XXXXXXXXX. Only characters and numbers to be masked space and special characters not be masked. In addition length of masked data should match actual data.
I think you can use a formula like this:
Local StringVar str := "";
Local NumberVar strLen := Length({User.Address});
Local NumberVar i;
For i := 1 To strLen Do
(
if (ChrW({User.Address}[i]) in (AscW("A") to AscW("Z"))) or (ChrW({User.Address}[i]) in (AscW("a") to AscW("z"))) or (ChrW({User.Address}[i]) in (AscW("0") to AscW("9"))) Then
str := str + "X"
else
str := str + {User.Address}[i];
);
str
Logic is correct.. I have done the same thing by using Mid function instead of chrW (). No need to use asw () either. Simply use range operator.
Logic is correct.. I have done the same thing by using Mid function instead of chrW (). Additional you can use formula as ReplicateString ("X",len(address));
Only problem is space will be also masked

TCP socket - sending control characters

I am trying to connect by a TCP/IP protocol to a program that is not made in Delphi, and I have no documentation on how to do it.
Using a service made in Delphi, I have monitored communication between two clients of the program, and write sendt text to a file, so I can play it back in my Delphi program.
I managed to send several things, but the problem is when I have to give this text:
SOH NUL ÜÜ NUL ENQ NUL Data ENQ NUL NUL NUL SOH NUL NUL NUL NUL 557
I see that there are special characters in string parts, and am having trouble sending these data.
I have tried to send it as a string and put the ASCII values but special characters are not working.
Here is part of my code:
TCPClient := TIdTCPClient.Create(nil);
TCPClient.Host := edtIP.Text;
Memo1.Lines.Clear;
TCPClient.Port := PORT;
TCPClient.ConnectTimeout := 20000;
TCPCliente.Connect;
Memo1.Lines.Add('Conectado con el Sevidor');
TCPClient.Socket.ReadTimeout := 20000;
edtTextoEnvio.Text := edtVersion.Text+' '+edtIP.Text+' '+
edtAlias.Text+' '+edtFuncion.Text+' 'edtPassword.Text;
TCPClient.Socket.WriteLnRFC(edtTextoEnvio.Text, IndyTextEncoding_8Bit);
Memo1.Lines.Add('Mensaje Enviado: '+edtTextoEnvio.Text);
TextoRecibido := TCPCliente.Socket.ReadLn(IndyTextEncoding_8Bit);
Memo1.Lines.Add('Mensaje Recibido: '+TextoRecibido);
TCPCliente.Socket.WriteLnRFC(NUNProc, IndyTextEncoding_8Bit);
Memo1.Lines.Add('Mensaje Enviado: '+NUNProc);
envioMensaje:= Char(1)+ Char(0)+'ÜÜ'+ Char(0)+ Char(5)+Char(0)+'Data'+Char()5)+Char(0)+Char(0)+Char(0)+Char(1)+CHar(0)+Char(0)+Char(0)+Char(0)+'557';
TCPCliente.Socket.WriteLnRFC(envioMensaje, IndyTextEncoding_8Bit); //here is the problem
TextoRecibido := TCPCliente.Socket.ReadLn(IndyTextEncoding_8Bit);
Memo1.Lines.Add('Mensaje Recibido: '+TextoRecibido);
Some of the string values that you want to send have non-ASCII characters in them, but sockets have no concept of text, only bytes. You have to encode the strings into bytes using an appropriate charset, then you can send the bytes as needed. Indy has functionality built-in to handle that for you (such as the TIdIOHandler.DefStringEncoding property, and the AByteEncoding parameter of string-based TIdIOHandler.Write...() and TIdIOHandler.Read...() methods). You are trying to take that into account, but you are using the wrong Indy encoding to do it. IndyTextEncoding_8bit is not really a charset, it is primarily intended as a workaround for dealing with 8bit binary data in text-based protocols, and should not be used in general code. What you need to do instead is identify the actual charset that the other party is expecting you to use. Different charsets encode the same non-ASCII characters in different ways (if at all), so you and the other party must agree on the same charset to encode and decode the strings, otherwise you are going to lose data.
That being said, there are different ways in Indy to accomplish what you are attempting. Given the data you have shown (it would have been better if you had shown the data actually captured by your monitoring service, or better from a packet sniffer like Wireshark), at a minimum I would suggest something more like the following:
TCPClient := TIdTCPClient.Create(nil);
TCPClient.Host := edtIP.Text;
TCPClient.Port := PORT;
TCPClient.ConnectTimeout := 20000;
TCPClient.ReadTimeout := 20000;
Memo1.Lines.Clear;
TCPClient.Connect;
TCPClient.IOHandler.DefStringEncoding := CharsetToEncoding('ISO-8859-1'); // <-- this is just a guess!
Memo1.Lines.Add('Conectado con el Sevidor');
edtTextoEnvio.Text := edtVersion.Text + ' ' + edtIP.Text + ' ' + edtAlias.Text + ' ' + edtFuncion.Text + ' ' + edtPassword.Text;
TCPClient.IOHandler.WriteLn(edtTextoEnvio.Text);
Memo1.Lines.Add('Mensaje Enviado: ' + edtTextoEnvio.Text);
TextoRecibido := TCPClient.IOHandler.ReadLn;
Memo1.Lines.Add('Mensaje Recibido: ' + TextoRecibido);
TCPClient.IOHandler.WriteLn(NUNProc);
Memo1.Lines.Add('Mensaje Enviado: ' + NUNProc);
TCPClient.IOHandler.WriteBufferOpen;
try
TCPClient.IOHandler.Write(Byte(1));
TCPClient.IOHandler.Write(Byte(0));
TCPClient.IOHandler.Write('ÜÜ');
TCPClient.IOHandler.Write(Byte(0));
TCPClient.IOHandler.Write(Byte(5));
TCPClient.IOHandler.Write(Byte(0));
TCPClient.IOHandler.Write('Data');
TCPClient.IOHandler.Write(Byte(5));
TCPClient.IOHandler.Write(Byte(0));
TCPClient.IOHandler.Write(Byte(0));
TCPClient.IOHandler.Write(Byte(0));
TCPClient.IOHandler.Write(Byte(1));
TCPClient.IOHandler.Write(Byte(0));
TCPClient.IOHandler.Write(Byte(0));
TCPClient.IOHandler.Write(Byte(0));
TCPClient.IOHandler.Write(Byte(0));
TCPClient.IOHandler.Write('557');
TCPClient.IOHandler.WriteLn;
TCPClient.IOHandler.WriteBufferClose;
except
TCPClient.IOHandler.WriteBufferCancel;
raise;
end;
TextoRecibido := TCPClient.IOHandler.ReadLn;
Memo1.Lines.Add('Mensaje Recibido: ' + TextoRecibido);
That being said, all of those nul bytes are more likely to be part of multi-byte binary integers, which you can send using TIdIOHandler.Write(Int16), TIdIOHandler.Write(Int32), etc:
TCPClient.IOHandler.WriteBufferOpen;
try
{
TCPClient.IOHandler.Write(Byte(1));
TCPClient.IOHandler.Write(Byte(0));
}
TCPClient.IOHandler.Write(Int16(1), False);
TCPClient.IOHandler.Write('ÜÜ');
TCPClient.IOHandler.Write(Byte(0));
{
TCPClient.IOHandler.Write(Byte(5));
TCPClient.IOHandler.Write(Byte(0));
}
TCPClient.IOHandler.Write(Int16(5), False);
TCPClient.IOHandler.Write('Data');
{
TCPClient.IOHandler.Write(Byte(5));
TCPClient.IOHandler.Write(Byte(0));
TCPClient.IOHandler.Write(Byte(0));
TCPClient.IOHandler.Write(Byte(0));
}
TCPClient.IOHandler.Write(Int32(5), False);
{
TCPClient.IOHandler.Write(Byte(1));
TCPClient.IOHandler.Write(Byte(0));
TCPClient.IOHandler.Write(Byte(0));
TCPClient.IOHandler.Write(Byte(0));
}
TCPClient.IOHandler.Write(Int32(1), False);
TCPClient.IOHandler.Write(Byte(0));
TCPClient.IOHandler.Write('557');
TCPClient.IOHandler.WriteLn;
TCPClient.IOHandler.WriteBufferClose;
except
TCPClient.IOHandler.WriteBufferCancel;
raise;
end;
And the ÜÜ is also very suspicious. It is most likely NOT actually a ÜÜ string, but more likely just bytes $DC $DC, 2 8-bit 220 values, a 16-bit 56540 value, etc.
Without clear documentation about the actual communication protocol, you are just making guesses, and you are likely to guess wrong at times. So I strongly suggest you contact the author of the other client app and ask for documentation. Or search online and see if a 3rd party has already written such documentation for that app. Otherwise you are just coding blind, and that is not the good option.