Bug 86386

Summary: A better Mid-function in BASIC
Product: LibreOffice Reporter: ge.huber
Component: BASICAssignee: Not Assigned <libreoffice-bugs>
Status: NEW --- QA Contact:
Severity: enhancement    
Priority: lowest CC: qubit
Version: 4.2.7.2 release   
Hardware: All   
OS: All   
See Also: https://bugs.freedesktop.org/show_bug.cgi?id=87834
Whiteboard:
i915 platform: i915 features:

Description ge.huber 2014-11-17 10:59:09 UTC
The Mid-function is less then optimal. 
 
Problems are 
 
- a negative length is treated like no length given 
- a start position of 0 results in error 5 
- a negative start positions returns !br0ken!! 
 
Taking inspiration from the way ruby handles strings, I propose improvements as follows. 
 
- Mid should take negative length which runs from left to right
- it should take negative start positions which are counted to the left, from the last position in the String i.e. the last Position is -1, the next to last is 2 etc.. 0 is the first position before the start of the string, as is  -Len(ofthestring) - 1

For details please refer to the definition of the Middle-function below which is largely backwards compatible to Mid, unless you count the throwing of errors and "!br0ken!!" as intended behaviour. It might break programs which rely on a negative length being treated as no length.
 
Sub MiddleDemo
Dim sText As String
sText = "abcdefghijkl"
Print "The Mid-function is " & Mid(sText, -1)
Print Middle(sText, 0, 3) 'the first two characters 
Print Middle(sText, -1, -4) 'the last four characters
Call Middle(sText, 6, -3, "456") 'replace three characters counted to the left from the sixth position
Print sText
End Sub

Function Middle(sText As String, Optional lStart As Long, Optional lLen As Long, Optional sInsert As String) As String
If IsMissing(lStart) Then lStart = 1 
Dim lLenText As Long 
lLenText = Len(sText) 
If IsMissing(lLen) Then 
  If lStart >= 0 Then 
    lLen = lLenText 'get whole text up to the end of the string
  Else
    lLen = -lLenText 'get whole text up to the start of the string
  End If
End If
Dim lMidStart As Long 'start of the string to return, we intend to use the orginal Mid-function
If lStart < 0 Then 
  lMidStart = lLenText + 1 + lStart 'convert negative position to corresponding positive position 
Else
  lMidStart = lStart
End If
Dim lMidLen As Long 'length of the string to return 
If lLen < 0 Then 'the string to return is on the left to lMidStart 
  lMidStart = lMidStart + 1 + lLen 'start of the string 
  lMidLen = -lLen
Else 'the string to return is right to lMidStart 
  lMidLen = lLen
End If 
If lMidStart < 1 Then 'start still negative, 
  lMidLen = lMidLen + lMidStart - 1 'length must be shortened, so that we cab use thr original Mid-function
  lMidStart = 1 'the string to return starts from the first position
End If  
'if lMidLen isn't positiv, return an empty string  
If lMidLen < 1 Then 'we can't give a negative length to the original Mid, because ist would return the whole string sText 
  Middle = ""
Else 'ready to use the original Mid 
  Middle = Mid(sText, lMidStart, lMidLen)
End If
If IsMissing(sInsert) Then Exit Function 'don't change the string sText, so we are done 
sText = Left(sText, lMidStart - 1) & sInsert & Mid(sText, lMidStart + lMidLen)
End Function
Comment 1 ge.huber 2014-11-17 11:09:07 UTC
Oops! Sorry the Middle-function was buggy. Here's correction.

Function Middle(sText As String, Optional lStart As Long, Optional lLen As Long, Optional sInsert As String) As String
'like Mid, but takes negative lLen which runs from left to right
'also negative lStart which are counted to the left, from the 
'last position in the String i.e. the last Position ist -1 
'On Error GoTo Err_Middle
If IsMissing(lStart) Then lStart = 1 
Dim lLenText As Long 
lLenText = Len(sText) 
If IsMissing(lLen) Then 
  Select Case lStart 
  Case > 0 
    lLen = lLenText 'get whole text up to the end of the string
  Case 0 
    lLen = lLenText + 1
  Case Else
    lLen = -lLenText 'get whole text up to the start of the string
  End Select
End If
Dim lMidStart As Long 'start of the string to return, we intend to use the orginal Mid-function
If lStart < 0 Then 
  lMidStart = lLenText + 1 + lStart 'convert negative position to corresponding positive position 
Else
  lMidStart = lStart
End If
Dim lMidLen As Long 'length of the string to return 
If lLen < 0 Then 'the string to return is left to lMidStart 
  lMidStart = lMidStart + 1 + lLen 'start of the string 
  lMidLen = -lLen
Else 'the string to return is right to lMidStart 
  lMidLen = lLen
End If 
If lMidStart < 1 Then 'start still negative, 
  lMidLen = lMidLen + lMidStart - 1 'length must be shortened, so that we cab use thr original Mid-function
  lMidStart = 1 'the string to return starts from the first position
End If  
'if lMidLen isn't positiv, return an empty string  
If lMidLen < 1 Then 'we can't give a negative length to the original Mid, because ist would return the whole string sText 
  Middle = ""
Else 'ready to use the original Mid 
  Middle = Mid(sText, lMidStart, lMidLen)
End If
If IsMissing(sInsert) Then Exit Function 'don't change the string sText, so we are done 
sText = Left(sText, lMidStart - 1) & sInsert & Mid(sText, lMidStart + lMidLen)
End Function
Comment 3 ge.huber 2014-11-20 12:59:52 UTC
Thanks for the link. 
 
I don't want to change the signature of Mid. I did this in the Middle function only because I don't know whether and how to return an l-value using BASIC. 
 
I assume there is no code which makes use of the undefined behaviour, if you use negative length. However I admit that my proposal might break existing code which - through error handling - relies on the fact that Mid throws an error when given a negative start position.

Here's my third try

Sub MiddleDemo
'The Mid-function is less then optimal
'Problems are
'- a negative length is treated like no length given 
'- a start position of 0 results in error 5 
'- a negative start positions returns !br0ken!! 
Dim sText As String
sText = "abcdefghijkl"
Print "The Mid-function is " & Mid(sText, -1)
Print Middle(sText, 0, 3) 'the first two characters 
Print Middle(sText, -1, -4) 'the last four characters
Call Middle(sText, 6, -3, "45678") 'replace three charactes counted to the left from the sixth position
Print sText
End Sub

Function Middle(sText As String, Optional lStart As Long, Optional lLen As Long, Optional sInsert As String) As String
'like Mid, but takes negative lLen which runs from left to right
'also negative lStart which are counted to the left, from the 
'last position in the String i.e. the last Position is -1 
'On Error GoTo Err_Middle
If IsMissing(lStart) Then lStart = 1 
Dim lLenText As Long 
lLenText = Len(sText) 
If IsMissing(lLen) Then 
  Select Case lStart 
  Case > 0 
    lLen = lLenText 'get whole text up to the end of the string
  Case 0 
    lLen = lLenText + 1
  Case Else
    'I don't know if this is a godd idea, or if it still should be lLen = lLenText
    lLen = -lLenText 'get whole text up to the start of the string
  End Select
End If
'we intend to use the orginal Mid-function
Dim lMidStart As Long 'so we calculate the start of the string to return in a coordinate system, which Mid understands  
If lStart < 0 Then 
  lMidStart = lLenText + 1 + lStart 'convert negative position to corresponding positive position 
Else
  lMidStart = lStart
End If
Dim lMidLen As Long 'length of the string to return 
If lLen < 0 Then 'the string to return is left to lMidStart 
  lMidStart = lMidStart + 1 + lLen 'start of the string 
  lMidLen = -lLen
Else 'the string to return is right to lMidStart 
  lMidLen = lLen
End If 
If lMidStart < 1 Then 'start still negative, 
  lMidLen = lMidLen + lMidStart - 1 'length must be shortened, so that we can use the original Mid-function
  lMidStart = 1 'the string to return starts from the first position
End If  
'if lMidLen isn't positiv, return an empty string  
If lMidLen < 1 Then 'we can't give a negative length to the original Mid, because it would return the whole string sText 
  Middle = ""
Else 'ready to use the original Mid 
  Middle = Mid(sText, lMidStart, lMidLen)
End If
If IsMissing(sInsert) Then Exit Function 'don't change the string sText, so we are done 
sText = Left(sText, lMidStart - 1) & Left(sInsert, lMidLen) & Mid(sText, lMidStart + lMidLen)
Exit_Middle:
Exit Function

Err_Middle:
Print StdErrMsg & CStr(Err) & " in Line " & CStr(Erl) & Chr$(13) & Chr$(10) & Error$ & " StringMod.Middle"
Resume Next
End Function
Comment 4 Robinson Tryon (qubit) 2014-11-23 13:04:36 UTC
(In reply to ge.huber from comment #0)
> The Mid-function is less then optimal. 
>  

I don't have any strong feelings about whether the Mid function needs improvement, but I'm always in favor of better interfaces for programming.

Except for small snippets, please toss code into a repository somewhere. Gitorious and GitHub are both good options, and will let you improve the code without pasting and re-pasting long comments here. As a bonus, you get indentation support, syntax highlighting, and all of those good things for free!

As Raal points out, the Mid function is defined in some OASIS docs covering the ODF spec, so changes should go up to them. That being said, it seems pretty tough to just go suggest something to the OASIS ODF TC, so I'm not opposed to some conversation here about whether this is a good idea or not, and what specific changes might be helpful.

Status -> NEW
Comment 5 ge.huber 2014-11-26 10:16:38 UTC
(In reply to Robinson Tryon (qubit) from comment #4)
> 
> As Raal points out, the Mid function is defined in some OASIS docs covering
> the ODF spec, so changes should go up to them. That being said, it seems
> pretty tough to just go suggest something to the OASIS ODF TC, 
 
Oh. I see. 
 
I'm not opposed to being referred to the proper addressee, just don't know how to communicate with them. That's assumed they let me at all.
  
However I'm not sure if OASIS is concerned here. I'm talking about the Mid function in BASIC. Is it necessarily bound to the MID function used in Calc documents? 
  
 
> so I'm not
> opposed to some conversation here about whether this is a good idea or not,
> and what specific changes might be helpful.

As I already admitted introducing a negative start might in theory break existing code. However there seems to be nothing in the definition which tells what to do with negative length. So of course whoever implemented the current behaviour was well within his rights. Actually he could have tried to start a game of nethack. ;-) http://everything2.com/title/%2523pragma However one might move beyond this point and start to make something potentially useful of it.
Comment 6 ge.huber 2014-11-26 10:27:08 UTC
Oh f*! What nonsense did I just write here? 
 
Well. Mid (i.a. the BASIC function) does not conform to OASIS MID (i.e.the Calc function) definition.
 
According to OASIS
 
 "If Start < 0, it returns an Error."
 
However
 
 Print "The Mid-function is " & Mid("quite ok.", -1) 
 
doesn't. 
 
Hope i got this right now.
Comment 7 Eike Rathke 2015-01-02 11:59:27 UTC
Please note that the ODF OpenFormula (ODFF) spreadsheet function MID() as defined in http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part2.html#MID is unrelated to the BASIC function Mid() which this issue is about.
Comment 8 Robinson Tryon (qubit) 2015-01-02 17:51:47 UTC
(In reply to Eike Rathke from comment #7)
> Please note that the ODF OpenFormula (ODFF) spreadsheet function MID() as
> defined in
> http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part2.
> html#MID is unrelated to the BASIC function Mid() which this issue is about.

Clarifying Summary

Use of freedesktop.org services, including Bugzilla, is subject to our Code of Conduct. How we collect and use information is described in our Privacy Policy.