Robdutoit
(Hey THIS is FUN)
2022-04-26 04:56 PM
Issue with Array not ignoring empty lines

I have created a very simple udf to read an ini file and install the printers listed in that ini file. However, for some reason, the code is returning an extra return or item in the array. I have had to exclude empty lines to get rid of that extra result.

Here is the udf

 Code:
Function PrintInstall($Section)

	$Handle = Freefilehandle ()
	
	If $handle > 0
		If Open ($handle, $iniFile) = 0
			$PrintServer = ReadProfileString($iniFile, "PrintServer", "Printer")
			$printerKeys = split(ReadProfileString($iniFile, $Section, ""),chr(10))
			FOR each $printerKey in $printerKeys
				IF $printerKey <> ""
					$printer = ReadProfileString($iniFile, $Section, $printerKey)
			? "Install the $Printer"
				ENDIF
			NEXT
			
			IF Close($handle)
				Beep
				? "Error closing file!"
			ENDIF
		ELSE
			? "Unable to open" + $iniFile
		ENDIF
	ELSE
		? "Unable to obtain a free system handle."
	ENDIF
	
EndFunction


I have to put this line in - IF $printerKey <> "" - otherwise the code returns an extra entry. I think it is something to do with empty lines in the ini file.

This is the ini file sections that this coding is reading

 Quote:

[PrintServer]

Printer = ServerName

[Accounting Printers]

printer1 = Ricoh
printer2 = Sharp
printer3 = Brother
printer4 = Xerox



ShaneEP
(MM club member)
2022-04-26 11:12 PM
Re: Issue with Array not ignoring empty lines

This may not be the source of your issue....But I think you're getting some stuff mixed up with Open/ReadLine and ReadProfileString functions. You don't have to do the filehandle, and Open stuff to use ReadProfileString.

ShaneEP
(MM club member)
2022-04-26 11:37 PM
Re: Issue with Array not ignoring empty lines

I am having a horrendous time trying to use ReadProfileString properly. I got your code down to the simplest form, and it's still giving me weird stuff back.

Can someone tell me why this code....
 Code:
$printers = Split(ReadProfileString(@ScriptDir + "\test.ini", "Accounting Printers", ""), chr(10))

For Each $printerKey in $printers
	? "Install the " + ReadProfileString(@ScriptDir + "\test.ini", "Accounting Printers", $printerKey)
Next

get $

Would give back this???
 Quote:
Install the Ricoh
Install the Sharp
Install the Brother
Install the Xerox
Install the printer1
printer2
printer3
printer4

For some reason it keeps on spitting out the key names at the end.

This is the INI files I'm using.
 Quote:
[PrintServer]
Printer = ServerName

[Accounting Printers]
printer1 = Ricoh
printer2 = Sharp
printer3 = Brother
printer4 = Xerox


AllenAdministrator
(KiX Supporter)
2022-04-27 01:00 AM
Re: Issue with Array not ignoring empty lines

I can confirm that I have seen this problem many times, and never once thought to post about it. I too have a line in my scripts that checks for a blank data to avoid the issue.

 Code:
$printers = Split(ReadProfileString(@ScriptDir + "\test.ini", "Accounting Printers", ""), chr(10))

For Each $printerKey in $printers
    if $printerkey<>""
	  ? "Install the " + ReadProfileString(@ScriptDir + "\test.ini", "Accounting Printers", $printerKey)
	 endif
Next

get $


I think it might be a bug.


AllenAdministrator
(KiX Supporter)
2022-04-27 01:06 AM
Re: Issue with Array not ignoring empty lines

 Code:
$printers = Split(ReadProfileString(@ScriptDir + "\test.ini", "Accounting Printers", ""), chr(10))
? ubound($printers)


Returns 4 instead of 3 using the provided test.ini.

The 4th result is what is causing the odd output...


AllenAdministrator
(KiX Supporter)
2022-04-27 01:41 AM
Re: Issue with Array not ignoring empty lines

If it is a bug... we have been dealing with it quietly for a long time. Glenn posted enumini in 2004, and his work around was

 Code:
if len($arrayitem) > 0


EnumINI
http://www.kixtart.org/forums/ubbthreads.php?ubb=showflat&Number=135385


ShaneEP
(MM club member)
2022-04-27 03:07 PM
Re: Issue with Array not ignoring empty lines

Found an example in an old forum post, where someone used Left($string, -1) to trim a space off the end, and it works (Trim() also seems to work). Seems that the readprofilesstring adds an extra line feed at the end, which causes the split to add the extra array item. Why it fills that blank item with the keys names, I can't say. Odd that I've used INI's so much in the past and don't recall ever running into this issue at all. Could just be an age issue, lol.

 Code:
$printers = Split(Left(ReadProfileString(@ScriptDir + "\test.ini", "AccountingPrinters", ""),-1), chr(10))

For Each $printerKey in $printers
	? "Install the " + ReadProfileString(@ScriptDir + "\test.ini", "AccountingPrinters", $printerKey)
Next

get $


ShaneEP
(MM club member)
2022-04-27 03:17 PM
Re: Issue with Array not ignoring empty lines

Back to the original post...Below is how I would handle the PrintInstall function. This seems to return the correct count of printer, regardless of blank lines in the INI file.

 Code:
$iniFile = @ScriptDir+"\test.ini"
$Section = "Accounting Printers"

PrintInstall($iniFile, $Section)

Function PrintInstall($iniFile, $Section)
	$PrintServer = ReadProfileString($iniFile, "PrintServer", "Printer")
	$printerKeys = Split(Left(ReadProfileString($iniFile, $Section, ""), -1),chr(10))
	FOR each $printerKey in $printerKeys
		$printer = ReadProfileString($iniFile, $Section, $printerKey)
		? "Install the $Printer"
	NEXT
EndFunction


mole
(Getting the hang of it)
2022-04-27 03:28 PM
Re: Issue with Array not ignoring empty lines

 Originally Posted By: ShaneEP
Could just be an age issue, lol.



My thought as well, as in my case, OLD age. I used to use INI's too and recall stumbling over this issue a few times. Getting trim() in the proper location seemed to solve the issue if my memory is working. Never crossed my mind it could be a bug, but chalked up to being a chemist and not a programmer!


Robdutoit
(Hey THIS is FUN)
2022-04-28 11:42 AM
Re: Issue with Array not ignoring empty lines

Never occurred to me that it could be a bug. I will try the trim and left options and see what happens. But the trim/left options would essentially be the same thing as the way that I have done it. A workaround to fix a bug in the readprofilestring command. I just assumed that I was doing something wrong in the coding.

What I will do first is get my code working without the handle, file open first to reduce unnecessary code. I just assumed that I needed it because I have always used handles when opening text files. Not sure why readprofilestring doesn't seem to need it as it's still opening a file which could be locked for some reason.


Robdutoit
(Hey THIS is FUN)
2022-04-28 12:22 PM
Re: Issue with Array not ignoring empty lines

I have removed all the handles, open, close commands and the script still works. So thank you for that. It makes it a lot easier to read the code without all those open/close handles.

I have tried the Left option and I prefer that to the way that I or Glenn did it as it removes an unnecessary if,endif statement. I have just made a note to use the Left option when using readprofilestrings to get round that bug.


Ruud van Velsen
(Hey THIS is FUN)
2022-04-28 02:22 PM
(NA) Re: Issue with Array not ignoring empty lines

Thanks for reporting - this is a side-effect of ReadProfileString adding a (superfluous) carriage-return after the last entry in a section. Basically, this is what it returns:

Ricoh(carriage-return)
Sharp(carriage-return)
Brother(carriage-return)
Xerox(carriage-return)

If you'd like this changed: let me know.


ShaneEP
(MM club member)
2022-04-28 03:51 PM
Re: (NA) Re: Issue with Array not ignoring empty lines

Rob...I debated on which way to go...Using the Left(string, -1) route, vs the Trim() route, and would recommend sticking with just the Left() method. In my mind, this altered the return values the least, and would avoid any potential issues if there were ever intentional spaces at the beginning or ending of values. That's just my 2 cents, even though Trim() may seem simpler.

Ruud, thanks for the feedback. In my opinion it seems odd to get the extra carriage return, but I can also understand why it was programmed that way. At this point it might be best to leave it as is, as it may break a lot of existing code that currently works around it. Some improved documentation on that function may be the better solution, but I'll let others chime in. I am curious however as to how all the section key names found their way into that last empty array item in my example above. I would have expected it to be blank.

 Quote:
Install the Ricoh
Install the Sharp
Install the Brother
Install the Xerox
Install the printer1
printer2
printer3
printer4


Robdutoit
(Hey THIS is FUN)
2022-04-28 04:49 PM
Re: (NA) Re: Issue with Array not ignoring empty lines

Shane - Sorry my post should have read Left, not Left Trim. I am using the Left option as it worked perfectly and got rid of the extra if,endif statement. So I didn't try the trim method. I will edit that post shortly.

Rudd - I would suggest changing the code for readprofilestring to remove the extra carriage return as technically it is a bug and it would seem that over the years many people have had to develop a workaround for it. I would expect anyone wanting to upgrade to the latest version of Kixtart to read the Release Notes to see what has changed before updating to the latest Kixtart variable. So I wouldn't expect it to break code as people should be reading the release notes before updating.

I don't know if you noticed, but I also created two more posts (in addition to the @Producttype bug which is being fixed in kix 4.69). That was for @domain variable and the syntax differences between AddPrinterConnection and DelPrinterConnection just in case you want to review whether it is worth making any changes there. Thanks.

The posts in question are:

http://kixtart.org/forums/ubbthreads.php?ubb=showflat&Number=214011#Post214011
http://kixtart.org/forums/ubbthreads.php?ubb=showflat&Number=214008#Post214008


Ruud van Velsen
(Hey THIS is FUN)
2022-04-28 06:20 PM
Re: (NA) Re: Issue with Array not ignoring empty lines

Hi Shane, considering the last entry in the array is empty, the last ReadProfileString call basically comes down to this:

ReadProfileString(@ScriptDir + "\test.ini", "Accounting Printers", "")

Which tells it to return all keynames in that section.


ShaneEP
(MM club member)
2022-04-28 07:33 PM
Re: (NA) Re: Issue with Array not ignoring empty lines

Ahh, yea that makes sense. Thanks for the explanation.

Glenn BarnasAdministrator
(KiX Supporter)
2022-04-28 11:12 PM
Re: (NA) Re: Issue with Array not ignoring empty lines

If you think about this, it isn't a bug. If you enumerate the sections or keys, each object returned is terminated by a newline:
 Code:
V1\nV2\nV3\n
You are using LINE TERMINATORS as DELIMITERS and have 3 delimiters, thus you have FOUR fields.

To properly parse the values, you need to translate LINE TERMINATION to DELIMITERS by removing the last (excess) line terminator. That's precisely what EnumIni() does, along with some error handling.
 Code:
; Get an INI enumeration of keys
$Tmp = ReadProfileString(@ScriptDir + '\test.ini', 'Accounting Printers', '')
; If we got data, remove the last line terminator and split on the \n delimiters
If Trim($Tmp)
  $aData = Split(Left($Tmp, Len($Tmp) - 1), Chr(10))
EndIf
; Enumerate the array of section keys and get the values
For Each $Key In $aData
  'Key: ' $Key ?
  'Val: ' ReadProfileString(@ScriptDir + '\test.ini', 'Accounting Printers', $Key) ?
Next
Quit 0
This outputs exactly what you would expect:
 Code:
Key: printer1
Val: Ricoh
Key: printer2
Val: Sharp
Key: printer3
Val: Brother
Key: printer4
Val: Xerox


If you don't remove the last line terminator, you have an extra delimiter, the last field after that is empty, so you get an additional enumeration string.

Basic coding - understand your data structure before you use it!

Glenn


AllenAdministrator
(KiX Supporter)
2022-04-29 12:50 AM
Re: (NA) Re: Issue with Array not ignoring empty lines

I agree it's not a bug with the further explanation, BUT... you have to admit getting a bunch of data, from seemingly nowhere, is jarring. We've all worked around it in our own ways, but the question is, do we need to continue to work around it? Ruud is offering us a "fix", and I think we should at least consider it. Is there any reason this "fix" would not be desirable? I haven't come up with a reason yet, but I'm open to hearing other's ideas on the subject.

Robdutoit
(Hey THIS is FUN)
2022-04-29 11:06 AM
Re: (NA) Re: Issue with Array not ignoring empty lines

I don't fully understand the code that you wrote Glenn. But my take on the issue would be this.

Is the way that readprofilestring currently works returning the intended result? I would not focus on whether it's technically a bug, but what the expected result of the command should be.

Using Shane's example he is getting Printer1-Printers 4 after listing the actual printers, which is clearly not the intended result of the readprofilestring. It sounds to me that the issue is the way that readprofilestring determines what is on a line creating four fields instead of three. So the code is technically correct, but is not returning the intended result unless an extra bit of coding is added such as trim, left or not equal to 0 etc to remove the last line.

The process is not important, the outcome is.


Glenn BarnasAdministrator
(KiX Supporter)
2022-04-29 05:57 PM
Re: (NA) Re: Issue with Array not ignoring empty lines

Allen - it's not broken so a "fix" would potentially affect all code that properly processes the results and is not a good solution IMO. If anything, the documentation should be updated to clarify that this is not a DELIMITED string but a String LIST of newline-terminated values. The data isn't coming from "nowhere", it's specifically requested because of poor data handling. The documentation, in fact, doesn't reference the returned format at all.

Ruud - the line terminator is Newline (Chr(10)) and not Carriage Return (Chr(13) as you noted.

Rob - Yes, it is returning the intended result.

This is what's happening, and why you aren't processing the data correctly.

Here's a simple example to help it make sense:
If you had a delimited string "A,B,C", you see that it's delimited with commas. There are TWO delimiters that result in 3 fields. The Split() on this string returns a 3-element array. Change this to "A,B,C," and you have 3 delimiters and 4 fields (the last is blank). A split on this returns a 4-element array. This is what you're getting from your code.

The result of ReadProfileString($File, $Section, '') does NOT return a delimited string. It returns a string list - a set of lines terminated with a Newline:
Line1 {NL}
Line2 {NL}
Line3 {NL}

If you look at this as a string, you get this: "Line1 {NL}Line2 {NL}Line3 {NL}" - 3 delimiters so 4 fields. The problem is that this isn't intended to be a delimiter because it isn't just a string, it's a string that contains a set of lines. When you split this using Chr(10) (newline) as a DELIMITER, it includes the last LINE TERMINATOR character and adds an "unseen" empty field. You cannot interchange the use of delimiters and line terminators.

To properly use the output of the ReadProfileString enumeration, you MUST remove the last Line Terminator (because it isn't a delimiter!). THEN and only then can you use the Line Terminator character as a delimiter, because the string now looks like "Line1 {NL}Line2 {NL}Line3" - 2 Delimiters, thus 3 fields.

If you don't eliminate the last LINE TERMINATOR, you wind up with an empty value. Consider what's happening:
 Code:
; Get the keys defined in the section
$aKeys = Split(ReadProfileString($File, $Section, '')
; Enumerate the keys
; just display the result of ReadProfileString for each key - assume there are 3 keys
; but we don't strip the final Line Terminator, so we have 4 elements in the array.
ReadProfileString($File, $Section, $aKey[0]) ?         ; Displays data from "Line 1" key
ReadProfileString($File, $Section, $aKey[1]) ?         ; Displays data from "Line 2" key
ReadProfileString($File, $Section, $aKey[2]) ?         ; Displays data from "Line 3" key
; You have 4 elements, the last is blank
ReadProfileString($File, $Section, $aKey[3]) ?         
; Displays the enumeration of File,Section because $aKey[3] is null! so outputs:
; Line1
; Line2
; Line3
Basically, either trim the last character from the output of the enumeration or use a pre-built function like EnumIni.

The Trim() in my example is not required - it's simply there to create a Boolean result to proceed when any data is returned or skip if a null / empty string was returned. I could have also used an If Not @ERROR instead. The most important thing is first getting the String List, then (if not empty), use Left() and Len() to trim the last line termination character. That logic essentially converts a String List to a Delimited String that can be split into the correct number of fields.

Shane's code is correct because he's removing the final delimiter, albeit through a "golf" mechanism that would not be obvious or intuitive. The left(String, -#) can trim a string by implicitly referencing the length from the end, so his and my examples provide the same result. I would not allow his example in production as it is not a clear/documented method, but something that was "discovered" and leveraged to reduce coding character counts for our Kix-golf games. When his code displays the excess list, it's when he didn't trim the final line terminator.

Finally, Trim() has no bearing whatsoever on processing INI files. You can have blank lines and comments (; or #) to your hearts content in an INI file and there will be zero impact on reading, writing, or enumerating it.




Glenn BarnasAdministrator
(KiX Supporter)
2022-04-29 06:05 PM
Re: (NA) Re: Issue with Array not ignoring empty lines

Just one more thing about this - it's not a ReadProfileString issue, it's a DATA issue. If you loaded a file - a set of lines - and then split it into an array of lines, you'd have the exact same issue - if the file contained a final line terminator, you'd have an empty final element. The only real difference is that we know that ReadProfileString ALWAYS returns the final line terminator, so we can simply snip it off. When reading a file, we'd need to first check whether the last char was a line terminator and remove it only if it was.

Glenn


ShaneEP
(MM club member)
2022-04-29 06:24 PM
Re: (NA) Re: Issue with Array not ignoring empty lines

I think we're all clear on what's going on, and why the extra trim/left is needed. The issue in my opinion is mostly lack of documentation. The guide simply says "If this parameter is empty, all key names in the section specified by section are returned".

(1) I wouldn't expect to have to split it by chr(10) going off just that text. Only way anyone would know, is to look at previous examples, or to display the return as a full string and guess if it's chr(10) or chr(13).

(2) I still wouldn't expect it to include the extra chr(10) at the end. This just adds the requirement of an additional unnecessary data massaging function in my opinion. No other enum functions return an extra element (I know those are arrays and not a string, but same concept).

As I stated above, I only suggest Left() over Trim() because if there happens to be an intentional space at the beginning or end of a key name (unlikely but possible), it would get trimmed off resulting in error. You could also do Left($string, Len($string)-1) if it makes you feel better. But not sure what you're referring to with the Left($string, -1) being a golf mechanism. The kix doc says "Specifying a negative value will cause Left to return the number of characters equal to the total length of the string minus the value specified". Nothing magical there. It is indeed clearly documented.


Glenn BarnasAdministrator
(KiX Supporter)
2022-04-30 04:58 PM
Re: (NA) Re: Issue with Array not ignoring empty lines

Ok- my apology! I memorized the manual - um, gulp - roughly 20 years ago? I don't recall seeing that, and my not-even-close-to-idetic memory didn't recall that!

The documentation definitely needs to clarify the format.

I'm 100% against changing this in the code because the format is NL Terminated and changing this to make it actually delimited would break every application that's properly processed the output, resulting in breaking the last key-name value.


AllenAdministrator
(KiX Supporter)
2022-04-30 06:23 PM
Re: (NA) Re: Issue with Array not ignoring empty lines

Before I say I'm vehemently opposed to this, I would like to know what Ruud had specifically in mind.

ShaneEP
(MM club member)
2022-05-02 12:49 AM
Re: (NA) Re: Issue with Array not ignoring empty lines

 Quote:
my apology! I memorized the manual - um, gulp - roughly 20 years ago
I totally understand! I didn't even remember how the readprofilestring() returned data until this week. And I couldn't tell you how many times I've used it successfully in the past.


Robdutoit
(Hey THIS is FUN)
2022-06-26 02:53 PM
Re: (NA) Re: Issue with Array not ignoring empty lines

I would concur that Ruud should decide how to proceed.

I don't mind either way, whether the function is changed or whether the documentation is updated to make people aware of the empty space issue. It's irrelevant whether the function is technically correct and the issue is Data - what is relevant is what programmers are expecting to get when using the function.

Judging by the fact that many experienced programmers are being caught out by this, clearly demonstrates the need to update the documentation or change how the function works.


Ruud van Velsen
(Hey THIS is FUN)
2022-09-12 03:47 PM
Re: (NA) Re: Issue with Array not ignoring empty lines

Short update on this (while working on the final version of 4.69): I'm going to leave this as-is. The 4.69 beta actually has a fix for it, but I agree this is iffy as it may break existing scripts.

If there's a pressing reason, we can revisit in a future update.

Thanks for the input!