How to close a stream properly? - f#

I have been assigned a task to write a program that will:
Open a file.
Read the content.
Replace a specific word with another word.
Save the changes to the file.
I know for sure that my code can open, read and replace words. The problem occurs when i add the "Save the changes to the file" - part. Here is the code:
open System.IO
//Getting the filename, needle and replace-word.
System.Console.WriteLine "What is the name of the file?"
let filename = string (System.Console.ReadLine ())
System.Console.WriteLine "What is your needle?"
let needle = string (System.Console.ReadLine ())
System.Console.WriteLine "What you want your needle replaced with?"
let replace = string (System.Console.ReadLine ())
//Saves the content of the file
let mutable saveLine = ""
//Opens a stream to read the file
let reader = File.OpenText filename
//Reads the file, and replaces the needle.
let printFile (reader : System.IO.StreamReader) =
while not(reader.EndOfStream) do
let line = reader.ReadLine ()
let lineReplace = line.Replace(needle,replace)
saveLine <- saveLine + lineReplace
printfn "%s" lineReplace
//Opens a stream to write to the file
let readerWrite = File.CreateText(filename)
//Writes to the file
let editFile (readerWrite : System.IO.StreamWriter) =
File.WriteAllText(filename,saveLine)
printf "%A" (printFile reader)
I get the error message "Sharing violation on path...", which makes me believe that the reading stream do not close properly. I have tried playing around with the structure of my code and tried different things for the .NET library, but i always get the same error message. Any help is much appreciated.

Streams are normally closed by calling Stream.Close() or disposing them.
System.IO has methods to read or write complete files from/to arrays of lines. This would shorten the operation to something like this:
File.ReadAllLines filePath
|> Array.map (fun line -> line.Replace(needle, replace))
|> fun editedLines -> File.WriteAllLines(filePath, editedLines)
What documentation are you using? Have a look at the MSDN documentation for System.IO and the similar MSDN documentations for various things in .NET/the CLR; these answer questions like this one quickly.

I retained most of your original code, although it's not very idiomatic. If you use use with disposable resources, .NET will clean up after you. See for example F# Docs and Fun&Profit, the latter also has a nice section on Expressions and syntax.
If you execute your code, you should get System.IO.IOException:
Unhandled Exception: System.IO.IOException: The process cannot access
the file 'C:\Users\xcs\Documents\Visual Studio
2015\Projects\StackOverflow6\ConsoleApplication11\bin\Release\testout.txt'
because it is being used by another process. at
System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
at System.IO.FileStream.Init(String path, FileMode mode, FileAccess
access, Int32 rights, Boolean useRights, FileShare share, Int32
bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String
msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)
at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess
access, FileShare share, Int32 bufferSize, FileOptions options, String
msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)
at System.IO.StreamWriter.CreateFile(String path, Boolean append,
Boolean checkHost) at System.IO.StreamWriter..ctor(String path,
Boolean append, Encoding encoding, Int32 bufferSize, Boolean
checkHost) at System.IO.StreamWriter..ctor(String path, Boolean
append) at System.IO.File.CreateText(String path) at
Program.op#46(Unit unitVar0) in C:\Users\xcs\Documents\Visual
Studio
2015\Projects\StackOverflow6\ConsoleApplication11\Program.fs:line 74
at Program.main(String[] argv) in C:\Users\xcs\Documents\Visual
Studio
2015\Projects\StackOverflow6\ConsoleApplication11\Program.fs:line 83
It starts at line 83, which is the call to the function, goes to line 74. Line 74 is the following: let readerWrite = File.CreateText(filename). Nowhere in your code have you closed reader. There is also another problem, you're opening a StreamWriter with File.CreateText. And then you're trying to write to this opened stream with File.WriteAllText, which opens the file, writes to it and closes it. So a bunch of IO handles are floating around there...
To quickly fix it consider the following:
//Getting the filename, needle and replace-word.
System.Console.WriteLine "What is the name of the file?"
let filename = string (System.Console.ReadLine ())
System.Console.WriteLine "What is your needle?"
let needle = string (System.Console.ReadLine ())
System.Console.WriteLine "What you want your needle replaced with?"
let replace = string (System.Console.ReadLine ())
//Saves the content of the file
//Opens a stream to read the file
//let reader = File.OpenText filename
//Reads the file, and replaces the needle.
let printFile (filename:string) (needle:string) (replace:string) =
let mutable saveLine = ""
use reader = File.OpenText filename //use will ensure that the stream is disposed once its out of scope, i.e. the functions exits
while not(reader.EndOfStream) do
let line = reader.ReadLine ()
let lineReplace = line.Replace(needle,replace)
saveLine <- saveLine + lineReplace + "\r\n" //you will need a newline character
printfn "%s" lineReplace
saveLine
//Writes to the file
let editFile filename saveLine =
File.WriteAllText(filename,saveLine) //you don't need a stream here, since File.WriteAllText will open, write, then close the file
let saveLine = printFile filename needle replace //read the file into saveLine
editFile filename saveLine //write saveLine into the file
It does a couple of things:
creates the StreamReader inside the printFile
binds it to reader with use, not let, to ensure it is closed once we don't need it anymore
add a linefeed to the string, since you insist rebuilding a mutable string
encapsulates the mutable saveLine inside the function
passes the needle and replace arguments explicitly
returns a new string to be used in 7.
gets rid of the Streamwriter by using File.WriteAllText and also passes in explicitly the filename and the string to write

Related

How can I convert a ZipWriter to Bytes in Rust?

I want to create a zip from a certain file, then i convert this zip (A buffer, not a file in the disk) to bytes, then to send it by a post body to an api.
Thats the code (simplifyed) just with the essencial, and from now to the next step, how can i convert it into bytes?
pub fn to_zip() {
let buf: &mut [u8] = &mut [0u8; 65536];
let w = std::io::Cursor::new(buf);
let mut zip = zip::ZipWriter::new(w);
// * Convert the buffer to bytes
zip.finish().unwrap();
}
Sorry for a maybe confuse code first time with Rust beeing loving it so far!
zip.finish().unwrap() gives you the Cursor that you used to create the ZipWriter. You can then use into_inner to go back to a &mut [u8].

writing binary files with streams

How to download image files via streams in the temp directory, I've following code and I'm stuck and need guidance with the seek and the count part. There are some wrapper approaches but I'm looking specifically for while loop approach for RAM efficiency reasons.
Writing
let tempFileName = Path.GetTempFileName()
let request = WebRequest.CreateHttp "http://example.com/image.png"
use response = request.GetResponse() :?> HttpWebResponse
use stream = response.GetResponseStream()
let buffer = Array.zeroCreate 1024
use reader = new BinaryReader(stream)
use memoryStream = new MemoryStream()
use fileStream = new FileStream(tempFileName, FileMode.Open)
while not (reader.PeekChar() <> -1) do
fileStream.Write(reader.ReadBytes(1024), 0, 1024)
return Ok (tempFileName)
First of all, I notice that although you're creating a buffer array, you're not actually using it. Second, when I look at the BinaryReader documentation, and specifically the documentation for the ReadBytes method, I notice that it takes an int parameter and returns a byte array. This must mean that it's allocating a new array every time, which seems to be the opposite of what you intend (since you mention RAM efficiency, I assume that what you actually want is to re-use the same buffer each time).
And one other observation: the ReadBytes method says that it might return an array smaller than the requested size, if there were fewer bytes available. Your code currently isn't handling that case.
All of these can be fixed, though, by switching to the BinaryReader.Read(byte[], int, int) method instead. With this method, your while loop would look something like the following:
while not (reader.PeekChar() <> -1) do
let bytesRead = reader.Read(buffer, 0, 1024)
fileStream.Write(buffer, 0, bytesRead)
And now that we're keeping track of how many bytes were read by each Read operation, we can get rid of the PeekChar call and save ourselves some time (calling PeekChar on something you're downloading is not without cost since the library has to download the next byte, then save it somewhere so it can be returned the next time you call Read). We can do that by checking how many bytes were read at the previous call: if it was 0, then that means we're at the end of the stream. To do this, we'll move the bytesRead variable out of the loop, which means making it a mutable variable that we'll re-use every time through the loop:
let mutable bytesRead = -1
while not (bytesRead = 0) do
bytesRead <- reader.Read(buffer, 0, 1024)
fileStream.Write(buffer, 0, bytesRead)
Or if you want to be slightly more explicit about the fact that you're skipping Write if bytesRead is 0, you could add an if block:
let mutable bytesRead = -1
while not (bytesRead = 0) do
bytesRead <- reader.Read(buffer, 0, 1024)
if bytesRead > 0 then
fileStream.Write(buffer, 0, bytesRead)
That last if statement isn't strictly necessary, though: FileStream.Write should just return without doing anything if it's asked to write 0 bytes. However, since that's not documented anywhere that I could find, I added the if statement in this last code sample just to be on the safe side.
As of .NET 4.6.2, there is System.IO.Stream#CopyTo method:
namespace FSharpBasics
module ImageCrawler =
open System.Net
open System.IO
open System.Text.RegularExpressions
let private myurl = "https://cdn.pixabay.com/photo/2016/07/06/15/29/math-1500720_960_720.jpg"
let crawler (url: string) =
let fileName = Regex.Match(url, #"\/([^\/]+)$", RegexOptions.RightToLeft).Groups.[1].Value
let request = WebRequest.CreateHttp url
let response = request.GetResponse()
use s = response.GetResponseStream()
use w = File.Create fileName
s.CopyTo w
w.Flush true
[<EntryPoint>]
let main argv =
printfn "JPEG file will be saved"
crawler myurl
printf "Saved"
0

Writing a function that replaces a string using OpenText, ReadLine and WriteLine

I have to write a function, given a filename, needle and a replace, that swaps the two strings in a given text document. The function has to use the System.IO.File.OpenText, WriteLine and ReadLine syntax. I'm currently stuck here, where the function seems to override given text document instead of replacing the needle.
open System
let fileReplace (filename : string) (needle : string) (replace : string) : unit =
try // uses try-with to catch fail-cases
let lines = seq {
use file = IO.File.OpenText filename // uses OpenText
while not file.EndOfStream // runs through the file
do yield file.ReadLine().Replace(needle, replace)
file.Close()
}
use writer = IO.File.CreateText filename // creates the file
for line in lines
do writer.Write line
with
_ -> failwith "Something went wrong opening this file" // uses failwith exception
let filename = #"C:\Users\....\abc.txt"
let needle = "string" // given string already appearing in the text
let replace = "string" // Whatever string that needs to be replaced
fileReplace filename needle replace
The problem with your code is that you are using lazy sequence when reading lines. When you use seq { .. }, the body is not actually evaluated until it is needed. In your example, this is when iterating over lines in a for loop - but before the code gets there, you call CreateText and overwrite the file!
You can fix this by using a list, which is evaluated immediately. You also need to replace Write with WriteLine, but the rest works!
let fileReplace (filename : string) (needle : string) (replace : string) : unit =
try // uses try-with to catch fail-cases
let lines =
[ use file = IO.File.OpenText filename // uses OpenText
while not file.EndOfStream do // runs through the file
yield file.ReadLine().Replace(needle, replace)
]
use writer = IO.File.CreateText filename // creates the file
for line in lines do
writer.WriteLine line
with
_ -> failwith "Something went wrong opening this file" // uses failwith exception
I also removed the Close call, because use takes care of that for you.
EDIT: I put back the required do keywords - I was confused by your formatting. Most people would write them at the end of the previous line as in my updated version.

How to pass F# a string and get the result back in c# [duplicate]

This question already has answers here:
Call F# code from C#
(4 answers)
Closed 8 years ago.
I am SQL developer and am really new to both F# and C#. I need help on how to pass a string to f# function below and to return the result from F# to C#.
Description of project:
I am using stanford postagger to tag a sentence with the parts of speech.
Reference link from where i copied this code.
(http://sergey-tihon.github.io/Stanford.NLP.NET/StanfordPOSTagger.html)
module File1
open java.io
open java.util
open edu.stanford.nlp.ling
open edu.stanford.nlp.tagger.maxent
// Path to the folder with models
let modelsDirectry =
__SOURCE_DIRECTORY__ + #'..\stanford-postagger-2013-06-20\models\'
// Loading POS Tagger
let tagger = MaxentTagger(modelsDirectry + 'wsj-0-18-bidirectional-nodistsim.tagger')
let tagTexrFromReader (reader:Reader) =
let sentances = MaxentTagger.tokenizeText(reader).toArray()
sentances |> Seq.iter (fun sentence ->
let taggedSentence = tagger.tagSentence(sentence :?> ArrayList)
printfn "%O" (Sentence.listToString(taggedSentence, false))
)
// Text for tagging
let text = System.Console.ReadLine();
tagTexrFromReader <| new StringReader(text)
it won't matter if C# or F# - do make a function that gets a string and returns ... let
s say an int, you just need something like this (put it in some MyModule.fs):
namespace MyNamespace
module MyModule =
// this is your function with one argument (a string named input) and result of int
let myFun (input : string) : int =
// do whatever you have to
5 // the value of the last line will be your result - in this case a integer 5
call it in from C#/.net with
int result = MyNamespace.MyModule.myFun ("Hallo");
I hope this helps you out a bit
For your example this would be:
let myFun (text : string) =
use reader = new StringReader(text)
tagTexrFromReader reader
as you'll have this in the module File1 you can just call it with var res = Fiel1.myFun(text);
BTW: use is in there because StringReader is IDisposable and using use F# will dispose the object when you exit the scope.
PS: is tagTexrFromReader a typo?

Extracting the text inside a docx file

I am using the below code to read .docx file and it is successfully extracting the text from the file. But the problem here is, it is just extracting the text. For example if my document data is like below
I am line 1
I am line 2 I am some other text
Then it is returning me like
I am line 1I am line 2I am some other text.
I just want as it is. How can I do that. Below is the code I am using now.
open System
open System.IO
open System.IO.Packaging
open System.Xml
let getDocxContent (path: string) =
use package = Package.Open(path, FileMode.Open)
let stream = package.GetPart(new Uri("/word/document.xml",UriKind.Relative)).GetStream()
stream.Seek(0L, SeekOrigin.Begin) |> ignore
let xmlDoc = new XmlDocument()
xmlDoc.Load(stream)
xmlDoc.DocumentElement.InnerText
let docData = getDocxContent #"C:\a1.docx"
printfn "%s" docData
You need to set the PreserveWhitespace property on your XmlDocument before loading it.
So change the code from:
let xmlDoc = new XmlDocument()
xmlDoc.Load(stream)
To:
let xmlDoc = new XmlDocument()
xmlDoc.PreserveWhitespace <- true
xmlDoc.Load(stream)

Resources