I want to be able to find and replace words in .docx files, I have working code but it doesn't actually do anything. Could anyone tell me how I can use this code to find and replace words?
open System
#light
#I #"C:\Users\netha\Documents\FSharpTest\packages\Microsoft.Office.Interop.Word\lib\net20"
#r "Microsoft.Office.Interop.Word.dll"
module FTEST1 =
open Microsoft.Office.Interop.Word
open System.IO
let comarg x = ref (box x)
let printDocument (doc : Document) =
printfn "Printing %s..." doc.Name
let findAndReplace (doc : Document, findText : string, replaceWithText : string) =
printfn "finding and replacing %s..." doc.Name
//options
let matchCase = comarg false
let matchWholeWord = comarg true
let matchWildCards = comarg false
let matchSoundsLike = comarg false
let matchAllWordForms = comarg false
let forward = comarg true
let format = comarg false
let matchKashida = comarg false
let matchDiacritics = comarg false
let matchAlefHamza = comarg false
let matchControl = comarg false
let read_only = comarg false
let visible = comarg true
let replace = comarg 2
let wrap = comarg 1
//execute find and replace
doc.Content.Find.Execute(
comarg findText,
matchCase,
matchWholeWord,
matchWildCards,
matchSoundsLike,
matchAllWordForms,
forward,
wrap,
format,
comarg replaceWithText,
replace,
matchKashida,
matchDiacritics,
matchAlefHamza,
matchControl)
let wordApp = new Microsoft.Office.Interop.Word.ApplicationClass(Visible = true)
let openDocument fileName =
wordApp.Documents.Open(comarg fileName)
// example useage
let closeDocument (doc : Document) =
printfn "Closing %sβ¦" doc.Name
doc.Close(SaveChanges = comarg false)
let findText = "test"
let replaceText = "McTesty"
let findandreplaceinfolders folder findText replaceText =
Directory.GetFiles(folder, "*.docx")
|> Array.iter (fun filePath ->
let doc = openDocument filePath
doc.Activate()
// printDocument doc
findAndReplace(doc, findText, replaceText)
closeDocument doc)
let currentFolder = __SOURCE_DIRECTORY__
printfn "Printing all files in [%s]..." currentFolder
findandreplaceinfolders currentFolder
wordApp.Quit()
printfn "Press any keyβ¦"
Console.ReadKey(true) |> ignore
No error messages, all seems to be working fine.
I believe you need to save the document.
doc.Close(SaveChanges = comarg false)
Should instead be
doc.Close(SaveChanges = comarg -1)
https://learn.microsoft.com/en-us/office/vba/api/word.wdsaveoptions
It appears that SaveChanges takes a WDSaveOptions
wdDoNotSaveChanges = 0 | wdPromptToSaveChanges = -2 | wdSaveChanges = -1
I originally recommended true because I thought SaveChanges was a boolean but it appears that it is actually an enum.
This code now fully works and will find and replace text in a word document
open System
#light
#I#"C:\Users\netha\Documents\FSharpTest\packages\Microsoft.Office.Interop.Word
\lib\net20"
#r "Microsoft.Office.Interop.Word.dll"
module FTEST1 =
open Microsoft.Office.Interop.Word
open System.IO
let comarg x = ref (box x)
let printDocument (doc : Document) =
printfn "Printing %s..." doc.Name
let findAndReplace (doc : Document, findText : string, replaceWithText : string) =
printfn "finding and replacing %s..." doc.Name
//options
let matchCase = comarg false
let matchWholeWord = comarg true
let matchWildCards = comarg false
let matchSoundsLike = comarg false
let matchAllWordForms = comarg false
let forward = comarg true
let format = comarg false
let matchKashida = comarg false
let matchDiacritics = comarg false
let matchAlefHamza = comarg false
let matchControl = comarg false
let read_only = comarg false
let visible = comarg true
let replace = comarg 2
let wrap = comarg 1
//execute find and replace
let res =
doc.Content.Find.Execute(
comarg findText,
matchCase,
matchWholeWord,
matchWildCards,
matchSoundsLike,
matchAllWordForms,
forward,
wrap,
format,
comarg replaceWithText,
replace,
matchKashida,
matchDiacritics,
matchAlefHamza,
matchControl)
printfn "Result of Execute is: %b" res
let wordApp = new Microsoft.Office.Interop.Word.ApplicationClass(Visible = true)
let openDocument fileName =
wordApp.Documents.Open(comarg fileName)
// example useage
let closeDocument (doc : Document) =
printfn "Closing %sβ¦" doc.Name
// wdDoNotSaveChanges = 0 | wdPromptToSaveChanges = -2 | wdSaveChanges = -1
doc.Close(SaveChanges = comarg -1)
let findText = "test"
let replaceText = "McTesty"
let findandreplaceinfolders folder findText replaceText =
Directory.GetFiles(folder, "*.docx")
|> Array.iter (fun filePath ->
let doc = openDocument filePath
doc.Activate()
// printDocument doc
printfn "I work"
findAndReplace(doc, findText, replaceText)
closeDocument doc)
let currentFolder = __SOURCE_DIRECTORY__
printfn "Printing all files in [%s]..." currentFolder
findandreplaceinfolders currentFolder findText replaceText
wordApp.Quit()
printfn "Press any keyβ¦"
Console.ReadKey(true) |> ignore
Related
let command = // some shell command execution that prints some log in terminal
command.description //This gives me the String representation
let textToSearch = "some string value "
//My approaches
let status = command.description.conatains(textToSearch)
XCTAssert(status, "The Text is not present!") // Fail
let status2 = command.description.range(of: textToSearch) != nil
XCTAssert(status, "The Text is not present!") // Fail
let command = Foo()
let description = command.description
let textToSearch = "some string value"
let status = description.localizedLowercase.contains(textToSearch.localizedLowercase)
XCTAssertTrue(status, "The Text is not present!")
Say you have a string that looks likes this:
let myStr = "Hello, this is a test String"
And you have two Ranges,
let rangeOne = myStr.range(of: "Hello") //lowerBound: 0, upperBound: 4
let rangeTwo = myStr.range(of: "this") //lowerBound: 7, upperBound: 10
Now you wish to replace those ranges of myStr with new characters, that may not be the same length as their original, you end up with this:
var myStr = "Hello, this is a test String"
let rangeOne = myStr.range(of: "Hello")!
let rangeTwo = myStr.range(of: "this")!
myStr.replaceSubrange(rangeOne, with: "Bonjour") //Bonjour, this is a test String
myStr.replaceSubrange(rangeTwo, with: "ce") //Bonjourceis is a test String
Because rangeTwo is based on the pre-altered String, it fails to properly replace it.
I could store the length of the replacement and use it to reconstruct a new range, but there is no guarantee that rangeOne will be the first to be replaced, nor that rangeOne will actually be first in the string.
The solution is the same as removing multiple items from an array by index in a loop.
Do it backwards
First replace rangeTwo then rangeOne
myStr.replaceSubrange(rangeTwo, with: "ce")
myStr.replaceSubrange(rangeOne, with: "Bonjour")
An alternative could be also replacingOccurrences(of:with:)
This problem can be solved by shifting the second range based on the length of first the replaced string.
Using your code, here is how you would do it:
var myStr = "Hello, this is a test String"
let rangeOne = myStr.range(of: "Hello")!
let rangeTwo = myStr.range(of: "this")!
let shift = "Bonjour".count - "Hello".count
let shiftedTwo = myStr.index(rangeTwo.lowerBound, offsetBy: shift)..<myStr.index(rangeTwo.upperBound, offsetBy: shift)
myStr.replaceSubrange(rangeOne, with: "Bonjour") // Bonjour, this is a test String
myStr.replaceSubrange(shiftedTwo, with: "ce") // Bonjour, ce is a test String
You can sort the range in descending order, then replace backwards, from the end to the start. So that any subsequent replacement will not be affect by the previous replacements. Also, it is safer to use replacingCharacters instead of replaceSubrange in case when dealing with multi-codepoints characters.
let myStr = "Hello, this is a test String"
var ranges = [myStr.range(of: "Hello")!,myStr.range(of: "this")!]
ranges.shuffle()
ranges.sort(by: {$1.lowerBound < $0.lowerBound}) //Sort in reverse order
let newWords : [String] = ["Bonjourπ","ce"].reversed()
var newStr = myStr
for i in 0..<ranges.count
{
let range = ranges[i]
//check overlap
if(ranges.contains(where: {$0.overlaps(range)}))
{
//Some range over lap
throw ...
}
let newWord = newWords[i]
newStr = newStr.replacingCharacters(in: range, with: newWord)
}
print(newStr)
My solution ended up being to take the ranges and replacement strings, work backwards and replace
extension String {
func replacingRanges(_ ranges: [NSRange], with insertions: [String]) -> String {
var copy = self
copy.replaceRanges(ranges, with: insertions)
return copy
}
mutating func replaceRanges(_ ranges: [NSRange], with insertions: [String]) {
var pairs = Array(zip(ranges, insertions))
pairs.sort(by: { $0.0.upperBound > $1.0.upperBound })
for (range, replacementText) in pairs {
guard let textRange = Range(range, in: self) else { continue }
replaceSubrange(textRange, with: replacementText)
}
}
}
Which works out to be useable like this
var myStr = "Hello, this is a test."
let rangeOne = NSRange(location: 0, length: 5) // βHelloβ
let rangeTwo = NSRange(location: 7, length: 4) // βthisβ
myStr.replaceRanges([rangeOne, rangeTwo], with: ["Bonjour", "ce"])
print(myStr) // Bonjour, ce is a test.
I want to get nTh char from target1 or target2 but using a reference char of abc String.
But result2 is not working as expected...
let abc = "abcde"
let target1 = "12345"
let target2 = "π
π
π
π
π
"
let m = abc.firstIndex(of: "c")!
let result1 = target1[m] //want "3" //working
let result2 = target2[m] //want "π
" //not working
String subscripting is a pretty bad idea, but if you insist on using it, you have to use distance and offset, not an absolute index. Like this:
let abc = "abcde"
let target1 = "12345"
let target2 = "π
π
π
π
π
"
let d = abc.distance(from: abc.startIndex, to: abc.firstIndex(of: "c")!)
let result1 = target1[target1.index(target1.startIndex, offsetBy:d)]
let result2 = target2[target2.index(target2.startIndex, offsetBy:d)]
Is there a way to get the list of preferred ( = saved) wifi's ssid on MacOS with Swift 3.0 ?
I found some example which are deprecated and are (surprisingly) only runnable on iOS.
Preferred Networks are stored in a plist as part of System Preferences NSUserDefaults. While I don't see an API to access these names directly, you can use the defaults shell command or NSTask to access the values:
defaults read /Library/Preferences/SystemConfiguration/com.apple.airport.preferences | grep SSIDString
Note that in this list are not only all of the SSIDs that the computer has connected to, but the list synced with any iCloud-enabled device.
Related discussion here: OS X Daily - See a List of All Wi-Fi Networks a Mac Has Previously Connected To
It might not be the most beautiful code ever, but it works in Swift 3.0.
func shell(arguments: [String] = []) -> (String? , Int32) {
let task = Process()
task.launchPath = "/bin/bash"
task.arguments = arguments
let pipe = Pipe()
task.standardOutput = pipe
task.standardError = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: .utf8)
let terminationStatus = task.terminationStatus
return (output, terminationStatus)
}
Extensions:
extension String {
func stringByReplacingFirstOccurrenceOfString(
target: String, withString replaceString: String) -> String
{
if let range = self.range(of: target) {
return self.replacingCharacters(in: range, with: replaceString)
}
return self
}
}
extension String {
func stringByReplacingLastOccurrenceOfString(
target: String, withString replaceString: String) -> String
{
if let range = self.range(of: target, options: String.CompareOptions.backwards) {
return self.replacingCharacters(in: range, with: replaceString)
}
return self
}
}
Get and clean the wifi's SSIDs
let (output, terminationStatus) = shell(arguments: ["-c", "defaults read /Library/Preferences/SystemConfiguration/com.apple.airport.preferences | grep SSIDString"])
if (terminationStatus == 0) {
let arrayOfWifi = output?.components(separatedBy: CharacterSet.newlines)
for var aWifi in arrayOfWifi! {
aWifi = aWifi.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
if (aWifi.hasPrefix("SSIDString = ")) {
aWifi = aWifi.stringByReplacingFirstOccurrenceOfString(target: "SSIDString = ", withString: "")
}
if (aWifi.hasPrefix("\"")) {
aWifi = aWifi.stringByReplacingFirstOccurrenceOfString(target: "\"", withString: "")
}
if (aWifi.hasSuffix("\";")) {
aWifi = aWifi.stringByReplacingLastOccurrenceOfString(target: "\";", withString: "")
}
if (aWifi.hasSuffix(";")) {
aWifi = aWifi.stringByReplacingLastOccurrenceOfString(target: ";", withString: "")
}
aWifi = aWifi.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
print(aWifi)
}
}
}
let firstSet = CharacterSet(charactersIn: "-()")
let secondSet = CharacterSet.whitespaces
I need to replace +48 (23) 899899 098 with +4823899899098.
let output = "+48 (23) 899899 098".components(separatedBy: firstSet).joined(separator: "")
but here I need to use two CharacterSets. How can I join them into one?
You can use union(_:) for that.
let output = "+48 (23) 899899 098".components(separatedBy: firstSet.union(secondSet)).joined()
Alternatively use regular expression:
let output = "+48 (23) 899899 098"
let trimmedOutput = output.replacingOccurrences(of: "[^0-9+]", with: "", options: .regularExpression)
// -> +4823899899098"
you can use function for phone formatting, so it would be reusable through app
//removes "(", ")", "-", " " etc. and adds "+" for region code format
extension String {
func phoneToString() -> String {
var value = "+"
for character in self.characters {
if Int(String(character)) != nil {
value = value + String(character)
}
}
return value
}
}
let phoneWithoutSpaces = "+48 (23) 899899 098".phoneToString()