<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd">
<script:module xmlns:script="http://openoffice.org/2000/script" script:name="SF_TextStream" script:language="StarBasic" script:moduleType="normal">REM =======================================================================================================================
REM ===			The ScriptForge library and its associated libraries are part of the LibreOffice project.				===
REM ===					Full documentation is available on https://help.libreoffice.org/								===
REM =======================================================================================================================

Option Compatible
Option ClassModule

Option Explicit

&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;
&apos;&apos;&apos;	SF_TextStream
&apos;&apos;&apos;	=============
&apos;&apos;&apos;		Class instantiated by the
&apos;&apos;&apos;			SF_FileSystem.CreateTextFile
&apos;&apos;&apos;			SF_FileSystem.OpenTextFile
&apos;&apos;&apos;		methods to facilitate the sequential processing of text files
&apos;&apos;&apos;		All open/read/write/close operations are presumed to happen during the same macro run
&apos;&apos;&apos;		The encoding to be used may be chosen by the user
&apos;&apos;&apos;			The list is in the Name column of https://www.iana.org/assignments/character-sets/character-sets.xhtml
&apos;&apos;&apos;			Note that probably not all values  are available
&apos;&apos;&apos;		Line delimiters may be chosen by the user
&apos;&apos;&apos;			In input, CR, LF or CR+LF are supported
&apos;&apos;&apos;			In output, the default value is the usual newline on the actual operating system (see SF_FileSystem.sfNEWLINE)
&apos;&apos;&apos;
&apos;&apos;&apos;		The design choices are largely inspired by
&apos;&apos;&apos;			https://docs.microsoft.com/en-us/office/vba/language/reference/user-interface-help/textstream-object
&apos;&apos;&apos;		The implementation is mainly based on the XTextInputStream and XTextOutputStream UNO interfaces
&apos;&apos;&apos;			https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1io_1_1XTextInputStream.html
&apos;&apos;&apos;			https://api.libreoffice.org/docs/idl/ref/interfacecom_1_1sun_1_1star_1_1io_1_1XTextOutputStream.html
&apos;&apos;&apos;
&apos;&apos;&apos;		Disk file systems and document&apos;s internal file systems
&apos;&apos;&apos;			All methods and properties are applicable without restrictions on both file systems.
&apos;&apos;&apos;			However, when updates are operated on text files embedded in a document, (with the WriteXXX() methods),
&apos;&apos;&apos;			the updates are first done on a copy of the original file. When the file is closed, the copy
&apos;&apos;&apos;			will overwrite the original file. The whole process is transparent for the user script.
&apos;&apos;&apos;
&apos;&apos;&apos;		Instantiation example:
&apos;&apos;&apos;			Dim FSO As Object, myFile As Object
&apos;&apos;&apos;				Set FSO = CreateScriptService(&quot;FileSystem&quot;)
&apos;&apos;&apos;				Set myFile = FSO.OpenTextFile(&quot;C:\Temp\ThisFile.txt&quot;, FSO.ForReading)	&apos;	Once per file
&apos;&apos;&apos;
&apos;&apos;&apos;		Detailed user documentation:
&apos;&apos;&apos;			https://help.libreoffice.org/latest/en-US/text/sbasic/shared/03/sf_textstream.html?DbPAR=BASIC
&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;

REM ================================================================== EXCEPTIONS

Const FILENOTOPENERROR			=	&quot;FILENOTOPENERROR&quot;		&apos;	The file is already closed
Const FILEOPENMODEERROR			=	&quot;FILEOPENMODEERROR&quot;		&apos;	The file is open in incompatible mode
Const ENDOFFILEERROR			=	&quot;ENDOFFILEERROR&quot;		&apos;	When file was read, an end-of-file was encountered

REM ============================================================= PRIVATE MEMBERS

Private [Me]				As Object
Private [_Parent]			As Object
Private ObjectType			As String		&apos; Must be TEXTSTREAM
Private ServiceName			As String
Private _FileName			As String		&apos; File where it is about in URL format
Private _IOMode				As Integer		&apos; ForReading, ForWriting or ForAppending
Private _Encoding			As String		&apos; https://www.iana.org/assignments/character-sets/character-sets.xhtml
Private _NewLine			As String		&apos; Line break in write mode
Private _FileExists			As Boolean		&apos; True if file exists before open
Private _LineNumber			As Long			&apos; Number of lines read or written
Private _FileHandler		As Object		&apos; com.sun.star.io.XInputStream or
											&apos; com.sun.star.io.XOutputStream or
											&apos; com.sun.star.io.XStream
Private _InputStream		As Object		&apos; com.sun.star.io.TextInputStream
Private _OutputStream		As Object		&apos; com.sun.star.io.TextOutputStream
Private _ForceBlankLine		As Boolean		&apos; Workaround: XTextInputStream misses last line if file ends with newline

&apos;	Document&apos;s file system only
Private _IsEmbeddedFile		As Boolean		&apos; True when concerned file is embedded in a document
Private _EmbeddedFileName	As String		&apos; When not blank and in update mode, the full embedded file name
											&apos; This file is initially copied in a temporary storage, modified by the actual class,
											&apos; and rewritten in the document when the textstream.CloseFile() method is run

REM ============================================================ MODULE CONSTANTS

REM ===================================================== CONSTRUCTOR/DESTRUCTOR

REM -----------------------------------------------------------------------------
Private Sub Class_Initialize()
	Set [Me] = Nothing
	Set [_Parent] = Nothing
	ObjectType = &quot;TEXTSTREAM&quot;
	ServiceName = &quot;ScriptForge.TextStream&quot;
	_FileName = &quot;&quot;
	_IOMode = -1
	_Encoding = &quot;&quot;
	_NewLine = &quot;&quot;
	_FileExists = False
	_LineNumber = 0
	Set _FileHandler = Nothing
	Set _InputStream = Nothing
	Set _OutputStream = Nothing
	_ForceBlankLine = False
	_IsEmbeddedFile = False
	_EmbeddedFileName = &quot;&quot;
End Sub		&apos;	ScriptForge.SF_TextStream Constructor

REM -----------------------------------------------------------------------------
Private Sub Class_Terminate()
	Call Class_Initialize()
End Sub		&apos;	ScriptForge.SF_TextStream Destructor

REM -----------------------------------------------------------------------------
Public Function Dispose() As Variant
	Call Class_Terminate()
	Set Dispose = Nothing
End Function	&apos;	ScriptForge.SF_TextStream Explicit Destructor

REM ================================================================== PROPERTIES

REM -----------------------------------------------------------------------------
Property Get AtEndOfStream() As Boolean
&apos;&apos;&apos;	In reading mode, True indicates that the end of the file has been reached
&apos;&apos;&apos;	In write and append modes, or if the file is not ready =&gt; always True
&apos;&apos;&apos;	The property should be invoked BEFORE each ReadLine() method:
&apos;&apos;&apos;		A ReadLine() executed while AtEndOfStream is True will raise an error
&apos;&apos;&apos;	Example:
&apos;&apos;&apos;		Dim sLine As String
&apos;&apos;&apos;			Do While Not myFile.AtEndOfStream
&apos;&apos;&apos;				sLine = myFile.ReadLine()
&apos;&apos;&apos;				&apos; ...
&apos;&apos;&apos;			Loop

	AtEndOfStream = _PropertyGet(&quot;AtEndOfStream&quot;)

End Property	&apos;	ScriptForge.SF_TextStream.AtEndOfStream

REM -----------------------------------------------------------------------------
Property Get Encoding() As String
&apos;&apos;&apos;	Returns the name of the text file either in url or in native operating system format
&apos;&apos;&apos;	Example:
&apos;&apos;&apos;		Dim myFile As Object
&apos;&apos;&apos;			FSO.FileNaming = &quot;SYS&quot;
&apos;&apos;&apos;			Set myFile = FSO.OpenTextFile(&quot;C:\Temp\myFile.txt&quot;)
&apos;&apos;&apos;			MsgBox myFile.Encoding		&apos;	UTF-8

	Encoding = _PropertyGet(&quot;Encoding&quot;)

End Property	&apos;	ScriptForge.SF_TextStream.Encoding

REM -----------------------------------------------------------------------------
Property Get FileName() As String
&apos;&apos;&apos;	Returns the name of the text file either in url or in native operating system format
&apos;&apos;&apos;	Example:
&apos;&apos;&apos;		Dim myFile As Object
&apos;&apos;&apos;			FSO.FileNaming = &quot;SYS&quot;
&apos;&apos;&apos;			Set myFile = FSO.OpenTextFile(&quot;C:\Temp\myFile.txt&quot;)
&apos;&apos;&apos;			MsgBox myFile.FileName		&apos;	C:\Temp\myFile.txt

	FileName = _PropertyGet(&quot;FileName&quot;)

End Property	&apos;	ScriptForge.SF_TextStream.FileName

REM -----------------------------------------------------------------------------
Property Get IOMode() As String
&apos;&apos;&apos;	Returns either &quot;READ&quot;, &quot;WRITE&quot; or &quot;APPEND&quot;
&apos;&apos;&apos;	Example:
&apos;&apos;&apos;		Dim myFile As Object
&apos;&apos;&apos;			FSO.FileNaming = &quot;SYS&quot;
&apos;&apos;&apos;			Set myFile = FSO.OpenTextFile(&quot;C:\Temp\myFile.txt&quot;)
&apos;&apos;&apos;			MsgBox myFile.IOMode		&apos;	READ

	IOMode = _PropertyGet(&quot;IOMode&quot;)

End Property	&apos;	ScriptForge.SF_TextStream.IOMode

REM -----------------------------------------------------------------------------
Property Get Line() As Long
&apos;&apos;&apos;	Returns the number of lines read or written so far
&apos;&apos;&apos;	Example:
&apos;&apos;&apos;		Dim myFile As Object
&apos;&apos;&apos;			FSO.FileNaming = &quot;SYS&quot;
&apos;&apos;&apos;			Set myFile = FSO.OpenTextFile(&quot;C:\Temp\myFile.txt&quot;, FSO.ForAppending)
&apos;&apos;&apos;			MsgBox myFile.Line		&apos;	The number of lines already present in myFile

	Line = _PropertyGet(&quot;Line&quot;)

End Property	&apos;	ScriptForge.SF_TextStream.Line

REM -----------------------------------------------------------------------------
Property Get NewLine() As Variant
&apos;&apos;&apos;	Returns the current character string to be inserted between 2 successive written lines
&apos;&apos;&apos;	The default value is the native line separator in the current operating system
&apos;&apos;&apos;	Example:
&apos;&apos;&apos;		MsgBox myFile.NewLine

	NewLine = _PropertyGet(&quot;NewLine&quot;)

End Property	&apos;	ScriptForge.SF_TextStream.NewLine (get)

REM -----------------------------------------------------------------------------
Property Let NewLine(ByVal pvLineBreak As Variant)
&apos;&apos;&apos;	Sets the current character string to be inserted between 2 successive written lines
&apos;&apos;&apos;	Example:
&apos;&apos;&apos;		myFile.NewLine = Chr(13) &amp; Chr(10)

Const cstThisSub = &quot;TextStream.setNewLine&quot;

	SF_Utils._EnterFunction(cstThisSub)
	If VarType(pvLineBreak) = V_STRING Then _NewLine = pvLineBreak
	SF_Utils._ExitFunction(cstThisSub)

End Property	&apos;	ScriptForge.SF_TextStream.NewLine (let)

REM ===================================================================== METHODS

REM -----------------------------------------------------------------------------
Public Function CloseFile() As Boolean
&apos;&apos;&apos; Empties the output buffer if relevant. Closes the actual input or output stream
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;	Returns:
&apos;&apos;&apos;		True if the closure was successful
&apos;&apos;&apos;	Exceptions:
&apos;&apos;&apos;		FILENOTOPENERROR		Nothing found to close
&apos;&apos;&apos;	Examples:
&apos;&apos;&apos;		myFile.CloseFile()

Dim bClose As Boolean   	    &apos;   Return value
Dim oSfa As Object				&apos;	com.sun.star.ucb.SimpleFileAccess
Const cstThisSub = &quot;TextStream.CloseFile&quot;
Const cstSubArgs = &quot;&quot;

	If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
	bClose = False

Check:
	SF_Utils._EnterFunction(cstThisSub, cstSubArgs)
	If Not _IsFileOpen() Then GoTo Finally

Try:
	If Not IsNull(_InputStream) Then _InputStream.closeInput()
	If Not IsNull(_OutputStream) Then
		_OutputStream.flush()
		_OutputStream.closeOutput()
	End If
	Set _InputStream = Nothing
	Set _OutputStream = Nothing
	Set _FileHandler = Nothing

	&apos;	Manage embedded file closure: copy temporary file to document internal storage
	If _IsEmbeddedFile Then
		Set oSfa = SF_Utils._GetUnoService(&quot;FileAccess&quot;)
		oSfa.copy(_FileName, _EmbeddedFileName)
	End If

	bClose = True

Finally:
	CloseFile = bClose
	SF_Utils._ExitFunction(cstThisSub)
	Exit Function
Catch:
	GoTo Finally
End Function    &apos;   ScriptForge.SF_TextStream.CloseFile

REM -----------------------------------------------------------------------------
Public Function GetProperty(Optional ByVal PropertyName As Variant) As Variant
&apos;&apos;&apos;	Return the actual value of the given property
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		PropertyName: the name of the property as a string
&apos;&apos;&apos;	Returns:
&apos;&apos;&apos;		The actual value of the property
&apos;&apos;&apos;		If the property does not exist, returns Null
&apos;&apos;&apos;	Exceptions:
&apos;&apos;&apos;		see the exceptions of the individual properties
&apos;&apos;&apos;	Examples:
&apos;&apos;&apos;		myModel.GetProperty(&quot;MyProperty&quot;)

Const cstThisSub = &quot;TextStream.GetProperty&quot;
Const cstSubArgs = &quot;&quot;

	If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
	GetProperty = Null

Check:
	If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
		If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
	End If

Try:
	GetProperty = _PropertyGet(PropertyName)

Finally:
	SF_Utils._ExitFunction(cstThisSub)
	Exit Function
Catch:
	GoTo Finally
End Function	&apos;	ScriptForge.SF_TextStream.GetProperty

REM -----------------------------------------------------------------------------
Public Function Methods() As Variant
&apos;&apos;&apos;	Return the list of public methods of the Model service as an array

	Methods = Array( _
					&quot;CloseFile&quot; _
					, &quot;ReadAll&quot; _
					, &quot;readLine&quot; _
					, &quot;SkipLine&quot; _
					, &quot;WriteBlankLines&quot; _
					, &quot;WriteLine&quot; _
					)

End Function	&apos;	ScriptForge.SF_TextStream.Methods

REM -----------------------------------------------------------------------------
Public Function Properties() As Variant
&apos;&apos;&apos;	Return the list or properties of the Timer class as an array

	Properties = Array( _
					&quot;AtEndOfStream&quot; _
					, &quot;Encoding&quot; _
					, &quot;FileName&quot; _
					, &quot;IOMode&quot; _
					, &quot;Line&quot; _
					, &quot;NewLine&quot; _
					)

End Function	&apos;	ScriptForge.SF_TextStream.Properties

REM -----------------------------------------------------------------------------
Public Function ReadAll() As String
&apos;&apos;&apos; Returns all the remaining lines in the text stream as one string. Line breaks are NOT removed
&apos;&apos;&apos;	The resulting string can be split in lines
&apos;&apos;&apos;		either by using the usual Split Basic builtin function if the line delimiter is known
&apos;&apos;&apos;		or with the SF_String.SplitLines method
&apos;&apos;&apos;	For large files, using the ReadAll method wastes memory resources.
&apos;&apos;&apos;		Other techniques should be used to input a file, such as reading a file line-by-line
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;	Returns:
&apos;&apos;&apos;		The read lines. The string may be empty.
&apos;&apos;&apos;		Note that the Line property in incremented only by 1
&apos;&apos;&apos;	Exceptions:
&apos;&apos;&apos;		FILENOTOPENERROR		File not open or already closed
&apos;&apos;&apos;		FILEOPENMODEERROR		File opened in write or append modes
&apos;&apos;&apos;		ENDOFFILEERROR			Previous reads already reached the end of the file
&apos;&apos;&apos;	Examples:
&apos;&apos;&apos;		Dim a As String
&apos;&apos;&apos;			a = myFile.ReadAll()

Dim sRead As String	   	    &apos;   Return value
Const cstThisSub = &quot;TextStream.ReadAll&quot;
Const cstSubArgs = &quot;&quot;

	If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
	sRead = &quot;&quot;

Check:
	If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
		If Not _IsFileOpen(&quot;READ&quot;) Then GoTo Finally
		If _InputStream.isEOF() Then GoTo CatchEOF
	End If

Try:
	sRead = _InputStream.readString(Array(), False)
	_LineNumber = _LineNumber + 1

Finally:
	ReadAll = sRead
	SF_Utils._ExitFunction(cstThisSub)
	Exit Function
Catch:
	GoTo Finally
CatchEOF:
	SF_Exception.RaiseFatal(ENDOFFILEERROR, FileName)
	GoTo Finally
End Function    &apos;   ScriptForge.SF_TextStream.ReadAll

REM -----------------------------------------------------------------------------
Public Function ReadLine() As String
&apos;&apos;&apos; Returns the next line in the text stream as a string. Line breaks are removed.
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;	Returns:
&apos;&apos;&apos;		The read line. The string may be empty.
&apos;&apos;&apos;	Exceptions:
&apos;&apos;&apos;		FILENOTOPENERROR		File not open or already closed
&apos;&apos;&apos;		FILEOPENMODEERROR		File opened in write or append modes
&apos;&apos;&apos;		ENDOFFILEERROR			Previous reads already reached the end of the file
&apos;&apos;&apos;	Examples:
&apos;&apos;&apos;		Dim a As String
&apos;&apos;&apos;			a = myFile.ReadLine()

Dim sRead As String	   	    &apos;   Return value
Dim iRead As Integer		&apos;	Length of line break
Const cstThisSub = &quot;TextStream.ReadLine&quot;
Const cstSubArgs = &quot;&quot;

	If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
	sRead = &quot;&quot;

Check:
	If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
		If Not _IsFileOpen(&quot;READ&quot;) Then GoTo Finally
		If AtEndOfStream Then GoTo CatchEOF
	End If

Try:
	&apos;	When the text file ends with a line break,
	&apos;	XTextInputStream.readLine() returns the line break together with the last line
	&apos;	Hence the workaround to force a blank line at the end
	If _ForceBlankLine Then
		sRead = &quot;&quot;
		_ForceBlankLine = False
	Else
		sRead = _InputStream.readLine()
		&apos;	The isEOF() is set immediately after having read the last line
		If _InputStream.isEOF() And Len(sRead) &gt; 0 Then
			iRead = 0
			If SF_String.EndsWith(sRead, SF_String.sfCRLF) Then
				iRead = 2
			ElseIf SF_String.EndsWith(sRead, SF_String.sfLF) Or SF_String.EndsWith(sRead, SF_String.sfCR) Then
				iRead = 1
			End If
			If iRead &gt; 0 Then
				sRead = Left(sRead, Len(sRead) - iRead)
				_ForceBlankLine = True	&apos;	Provision for a last empty line at the next read loop
			End If
		End If
	End If
	_LineNumber = _LineNumber + 1

Finally:
	ReadLine = sRead
	SF_Utils._ExitFunction(cstThisSub)
	Exit Function
Catch:
	GoTo Finally
CatchEOF:
	SF_Exception.RaiseFatal(ENDOFFILEERROR, FileName)
	GoTo Finally
End Function    &apos;   ScriptForge.SF_TextStream.ReadLine

REM -----------------------------------------------------------------------------
Public Function SetProperty(Optional ByVal PropertyName As Variant _
								, Optional ByRef Value As Variant _
								) As Boolean
&apos;&apos;&apos;	Set a new value to the given property
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		PropertyName: the name of the property as a string
&apos;&apos;&apos;		Value: its new value
&apos;&apos;&apos;	Exceptions
&apos;&apos;&apos;		ARGUMENTERROR		The property does not exist

Dim bSet As Boolean			&apos;	Return value
Const cstThisSub = &quot;TextStream.SetProperty&quot;
Const cstSubArgs = &quot;PropertyName, Value&quot;

	If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
	bSet = False

Check:
	If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
		If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
	End If

Try:
	bSet = True
	Select Case UCase(PropertyName)
		Case &quot;NEWLINE&quot;
			If Not SF_Utils._Validate(Value, &quot;Value&quot;, V_STRING) Then GoTo Catch
			NewLine = Value
		Case Else
			bSet = False
	End Select

Finally:
	SetProperty = bSet
	SF_Utils._ExitFunction(cstThisSub)
	Exit Function
Catch:
	GoTo Finally
End Function	&apos;	ScriptForge.SF_TextStream.SetProperty

REM -----------------------------------------------------------------------------
Public Sub SkipLine()
&apos;&apos;&apos; Skips the next line when reading a TextStream file.
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;	Exceptions:
&apos;&apos;&apos;		FILENOTOPENERROR		File not open or already closed
&apos;&apos;&apos;		FILEOPENMODEERROR		File opened in write or append modes
&apos;&apos;&apos;		ENDOFFILEERROR			Previous reads already reached the end of the file
&apos;&apos;&apos;	Examples:
&apos;&apos;&apos;		myFile.SkipLine()

Dim sRead As String	   	    &apos;   Read buffer
Const cstThisSub = &quot;TextStream.SkipLine&quot;
Const cstSubArgs = &quot;&quot;

	If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch

Check:
	If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
		If Not _IsFileOpen(&quot;READ&quot;) Then GoTo Finally
		If Not _ForceBlankLine Then		&apos;	The file ends with a newline =&gt; return one empty line more
			If _InputStream.isEOF() Then GoTo CatchEOF
		End If
	End If

Try:
	sRead = ReadLine()

Finally:
	SF_Utils._ExitFunction(cstThisSub)
	Exit Sub
Catch:
	GoTo Finally
CatchEOF:
	SF_Exception.RaiseFatal(ENDOFFILEERROR, FileName)
	GoTo Finally
End Sub		    &apos;   ScriptForge.SF_TextStream.SkipLine

REM -----------------------------------------------------------------------------
Public Sub WriteBlankLines(Optional ByVal Lines As Variant)
&apos;&apos;&apos; Writes a number of empty lines in the output stream
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		Lines: the number of lines to write
&apos;&apos;&apos;	Returns:
&apos;&apos;&apos;	Exceptions:
&apos;&apos;&apos;		FILENOTOPENERROR		File not open or already closed
&apos;&apos;&apos;		FILEOPENMODEERROR		File opened in read mode
&apos;&apos;&apos;	Examples:
&apos;&apos;&apos;			myFile.WriteBlankLines(10)
Dim i As Long
Const cstThisSub = &quot;TextStream.WriteBlankLines&quot;
Const cstSubArgs = &quot;Lines&quot;

	If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch

Check:
	If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
		If Not _IsFileOpen(&quot;WRITE&quot;) Then GoTo Finally
		If Not SF_Utils._Validate(Lines, &quot;Lines&quot;, V_NUMERIC) Then GoTo Finally
	End If

Try:
	For i = 1 To Lines
		_OutputStream.writeString(_NewLine)
	Next i
	_LineNumber = _LineNumber + Lines

Finally:
	SF_Utils._ExitFunction(cstThisSub)
	Exit Sub
Catch:
	GoTo Finally
End Sub		    &apos;   ScriptForge.SF_TextStream.WriteBlankLines

REM -----------------------------------------------------------------------------
Public Sub WriteLine(Optional ByVal Line As Variant)
&apos;&apos;&apos; Writes the given line to the output stream. A newline is inserted if relevant
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		Line: the line to write, may be empty
&apos;&apos;&apos;	Returns:
&apos;&apos;&apos;	Exceptions:
&apos;&apos;&apos;		FILENOTOPENERROR		File not open or already closed
&apos;&apos;&apos;		FILEOPENMODEERROR		File opened in in read mode
&apos;&apos;&apos;	Examples:
&apos;&apos;&apos;			myFile.WriteLine(&quot;Next line&quot;)
Dim i As Long
Const cstThisSub = &quot;TextStream.WriteLine&quot;
Const cstSubArgs = &quot;Line&quot;

	If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch

Check:
	If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
		If Not _IsFileOpen(&quot;WRITE&quot;) Then GoTo Finally
		If Not SF_Utils._Validate(Line, &quot;Line&quot;, V_STRING) Then GoTo Finally
	End If

Try:
	_OutputStream.writeString(Iif(_LineNumber &gt; 0, _NewLine, &quot;&quot;) &amp; Line)
	_LineNumber = _LineNumber + 1

Finally:
	SF_Utils._ExitFunction(cstThisSub)
	Exit Sub
Catch:
	GoTo Finally
End Sub		    &apos;   ScriptForge.SF_TextStream.WriteLine

REM =========================================================== PRIVATE FUNCTIONS

REM -----------------------------------------------------------------------------
Public Sub _Initialize()
&apos;&apos;&apos;	Opens file and setup input and/or output streams (ForAppending requires both)

Dim oSfa As Object				&apos;	com.sun.star.ucb.SimpleFileAccess

	&apos;	Default newline related to current operating system
	_NewLine = SF_String.sfNEWLINE

	Set oSfa = SF_Utils._GetUNOService(&quot;FileAccess&quot;)

	&apos;	Setup input and/or output streams based on READ/WRITE/APPEND IO modes
	Select Case _IOMode
		Case SF_FileSystem.ForReading
			Set _FileHandler = oSfa.openFileRead(_FileName)
			Set _InputStream = CreateUnoService(&quot;com.sun.star.io.TextInputStream&quot;)
			_InputStream.setInputStream(_FileHandler)
		Case SF_FileSystem.ForWriting
			&apos;	Output file is deleted beforehand
			If _FileExists Then oSfa.kill(_FileName)
			Set _FileHandler = oSfa.openFileWrite(_FileName)
			Set _OutputStream = CreateUnoService(&quot;com.sun.star.io.TextOutputStream&quot;)
			_OutputStream.setOutputStream(_FileHandler)
		Case SF_FileSystem.ForAppending
			Set _FileHandler = oSfa.openFileReadWrite(_FileName)
			Set _InputStream = CreateUnoService(&quot;com.sun.star.io.TextInputStream&quot;)
			Set _OutputStream = CreateUnoService(&quot;com.sun.star.io.TextOutputStream&quot;)
			_InputStream.setInputStream(_FileHandler)
			&apos;	Position at end of file: Skip and count existing lines
			_LineNumber = 0
			Do While Not _InputStream.isEOF()
				_InputStream.readLine()
				_LineNumber = _LineNumber + 1
			Loop
			_OutputStream.setOutputStream(_FileHandler)
	End Select

	If _Encoding = &quot;&quot; Then _Encoding = &quot;UTF-8&quot;
	If Not IsNull(_InputStream) Then _InputStream.setEncoding(_Encoding)
	If Not IsNull(_OutputStream) Then _OutputStream.setEncoding(_Encoding)

End Sub			&apos;	ScriptForge.SF_TextStream._Initialize

REM -----------------------------------------------------------------------------
Private Function _IsFileOpen(Optional ByVal psMode As String) As Boolean
&apos;&apos;&apos;	Checks if file is open with the right mode (READ or WRITE)
&apos;&apos;&apos;	Raises an exception if the file is not open at all or not in the right mode
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		psMode: READ or WRITE or zero-length string
&apos;&apos;&apos;	Exceptions:
&apos;&apos;&apos;		FILENOTOPENERROR		File not open or already closed
&apos;&apos;&apos;		FILEOPENMODEERROR		File opened in incompatible mode

	_IsFileOpen = False
	If IsMissing(psMode) Then psMode = &quot;&quot;
	If IsNull(_InputStream) And IsNull(_OutputStream) Then GoTo CatchNotOpen
	Select Case psMode
		Case &quot;READ&quot;
			If IsNull(_InputStream) Then GoTo CatchOpenMode
			If _IOMode &lt;&gt; SF_FileSystem.ForReading Then GoTo CatchOpenMode
		Case &quot;WRITE&quot;
			If IsNull(_OutputStream) Then GoTo CatchOpenMode
			If _IOMode = SF_FileSystem.ForReading Then GoTo CatchOpenMode
		Case Else
	End Select
	_IsFileOpen = True

Finally:
	Exit Function
CatchNotOpen:
	SF_Exception.RaiseFatal(FILENOTOPENERROR, FileName)
	GoTo Finally
CatchOpenMode:
	SF_Exception.RaiseFatal(FILEOPENMODEERROR, FileName, IOMode)
	GoTo Finally
End Function	&apos;	ScriptForge.SF_TextStream._IsFileOpen

REM -----------------------------------------------------------------------------
Private Function _PropertyGet(Optional ByVal psProperty As String)
&apos;&apos;&apos;	Return the value of the named property
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		psProperty: the name of the property

Dim cstThisSub As String
Dim cstSubArgs As String

	cstThisSub = &quot;TextStream.get&quot; &amp; psProperty
	cstSubArgs = &quot;&quot;
	SF_Utils._EnterFunction(cstThisSub, cstSubArgs)

	Select Case UCase(psProperty)
		Case UCase(&quot;AtEndOfStream&quot;)
			Select Case _IOMode
				Case SF_FileSystem.ForReading
					If IsNull(_InputStream) Then _PropertyGet = True Else _PropertyGet = CBool(_InputStream.isEOF() And Not _ForceBlankLine)
				Case Else	:	_PropertyGet = True
			End Select
		Case UCase(&quot;Encoding&quot;)
			_PropertyGet = _Encoding
		Case UCase(&quot;FileName&quot;)
			&apos;	Requested is the user visible file name in FileNaming notation
			_PropertyGet = SF_FileSystem._ConvertFromUrl(Iif(_IsEmbeddedFile, _EmbeddedFileName, _FileName))
		Case UCase(&quot;IOMode&quot;)
			With SF_FileSystem
				Select Case _IOMode
					Case .ForReading		: _PropertyGet = &quot;READ&quot;
					Case .ForWriting		: _PropertyGet = &quot;WRITE&quot;
					Case .ForAppending		: _PropertyGet = &quot;APPEND&quot;
					Case Else				: _PropertyGet = &quot;&quot;
				End Select
			End With
		Case UCase(&quot;Line&quot;)
			_PropertyGet = _LineNumber
		Case UCase(&quot;NewLine&quot;)
			_PropertyGet = _NewLine
		Case Else
			_PropertyGet = Null
	End Select

Finally:
	SF_Utils._ExitFunction(cstThisSub)
	Exit Function
End Function	&apos;	ScriptForge.SF_TextStream._PropertyGet

REM -----------------------------------------------------------------------------
Private Function _Repr() As String
&apos;&apos;&apos;	Convert the TextStream instance to a readable string, typically for debugging purposes (DebugPrint ...)
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;	Return:
&apos;&apos;&apos;		&quot;[TextStream]: File name, IOMode, LineNumber&quot;

	_Repr = &quot;[TextStream]: &quot; &amp; FileName &amp; &quot;,&quot; &amp; IOMode &amp; &quot;,&quot; &amp; CStr(Line)

End Function	&apos;	ScriptForge.SF_TextStream._Repr

REM ============================================ END OF SCRIPTFORGE.SF_TextStream
</script:module>