I'm receiving a string from the server in the following format:
118|...message...215|...message2...
Basically, it's the message length followed by a pipe and the message itself, repeated for multiple messages. The message is encoded UTF16.
I'm looking for a way to parse this in Swift. I know I could cast this as NSString and use standard indexes/ranges on that because UTF16 is what NSString uses, but I'm wondering what is the Swift way to handle this? I can't seem to find a way to pull a substring out of a String based on a UTF16 encoding.
Update
I'm not trying to initialize a String with raw UTF16 Data (there's plenty of ways to do that). I already have the string, so I'm trying to take a String in the above format and parse it. The issue I have is that the message length given to me by the server is based on UTF16. I can't simply extract the length and call String.advance(messageLength) on the Index because the length I've been given doesn't match the grapheme clusters that Swift advances on. My issue is that I can't extract from the string the message in Swift. I have to instead cast it over to NSString and then use "normal" NSRange on it. My question is how do I pull the substring out by extracting a range based on my search for the first pipe, and then use the length provided by the parser in UTF16.
This is all extremely simple to do with NSString. Not sure how it can be done in pure Swift (or if it can be done).
Here is my take on parsing the messages out of the string. I had to change your lengths to work with the string.
let message = "13|...message...14|...message2..."
let utf16 = message.utf16
var startingIndex = message.utf16.startIndex
var travellingIndex = message.utf16.startIndex
var messages = [String]()
var messageLength: Int
while travellingIndex != message.utf16.endIndex {
// Start walking through each character
if let char = String(utf16[travellingIndex..<travellingIndex.successor()]) {
// When we find the pipe symbol try to parse out the message length
if char == "|" {
if let stringNumber = Int(String(utf16[startingIndex..<travellingIndex])) {
messageLength = stringNumber
// We found the lenght, now skip the pipe character
startingIndex = travellingIndex.successor()
// move the travelingIndex to the end of the message
travellingIndex = travellingIndex.advancedBy(messageLength)
// get the message and put it into an array
if let message = String(utf16[startingIndex...travellingIndex]) {
messages.append(message)
startingIndex = travellingIndex.successor()
}
}
}
}
travellingIndex = travellingIndex.successor()
}
print(messages)
The output I get at the end is:
["...message...", "...message2..."]
The Foundation framework extends String to be initialisable from data:
import Foundation
let string = String(data: data, encoding: NSUTF16StringEncoding)
Getting rid of Foundation is not possible unless you implement the decoding yourself. Note that with Swift going open-source, Foundation is getting reimplemented without Objective-C dependency here.
EDIT: Thanks, Martin R, the link you provided is indeed working in pure Swift :D
EDIT:
There is the utf16 property of a String whose count property is the length in UTF16. Here is a simple parser for your purpose, efficiency isn't great, but it gets the job done:
func getMessages(var string: String) -> [String]? {
func getMessage(string: String) -> (message: String, rest: String)? {
guard let
index = string.characters.indexOf("|"),
length = Int(String(string.characters.prefixUpTo(index)))
else { return nil }
let msgRest = String(string.characters.suffixFrom(index.successor()))
return (String(msgRest.utf16.prefix(length)), String(msgRest.utf16.dropFirst(length)))
}
var messages : [String] = []
while let (message, rest) = getMessage(string) {
string = rest
messages.append(message)
}
return messages
}
func stringForMessages(messages: [String]) -> String {
return messages.map{ "\($0.utf16.count)|\($0)" }.joinWithSeparator("")
}
let messages = [
"123",
"ππ½ππ½ππ½",
"πππ
πΏ",
"6π΄β½οΈ"
]
let string = stringForMessages(messages)
let received = getMessages(string)
messages // ["123", "ππ½ππ½ππ½", "πππ
πΏ", "6π΄β½οΈ"]
I actually tried making it more efficient, but Swift's String mechanics pushed against it.. I challenge anyone to create a beautiful efficient crash-safe parser for this..
Related
I have a code like this :
// Language = Dart
var someVariable = 'Hello';
var someOtherVariable = 'World';
var str = 'somedomain?x=${someVariable}&y=${someOtherVariable}';
return str;
// Expected:
// somedomain?x=Hello&y=World;
// Actual
// somedomain?x=Hello
If I replace the & character with any alphabets, it is able to successfully concatenate. What am I doing wrong.
This is the actual code which I used in FlutterFlow, and am having issues with:
Future<String> getEventUrlFromReference(BuildContext context, DocumentReference? eventReference) async {
var userId = currentUser?.uid as String;
return "https://somedomain.com/event?eventReference=${eventReference?.id}" + "&invitedBy="+userId;
}
// result: https://somedomain.com/event?eventReference=referencevalue
This was a string encoding issue. I was using the result of my function/code as body text in sms://<number>?&body=<string_containigng_&_character>; The text which is appended to the sms text truncates at the & character, and I made a mistake assuming it's a string concatenation issue.
I was trying to make a node's program that takes a string, and gets all of the content inside it:
var str = "Hello {world}!";
console.log(getBracketSubstrings(str)); // => ['world']
It works, but when I do:
var str = "Hello {world{!}}";
console.log(getBracketSubstrings(str)); // => ['world{!']
It returns ['world{!}'], when I want it to return:
['world{!}']
Is there anyway to do this to a string in nodes?
You could use a pattern with a capture group, matching from { followed by any char except a closing curly using [^}]* until you encounter a }
{([^}]*)}
See a regex demo
const getBracketSubstrings = s => Array.from(s.matchAll(/{([^}]*)}/g), x => x[1]);
console.log(getBracketSubstrings("Hello {world}!"));
console.log(getBracketSubstrings("Hello {world{!}}"));
I was using this, in Swift 1.2
let urlwithPercentEscapes = myurlstring.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
This now gives me a warning asking me to use
stringByAddingPercentEncodingWithAllowedCharacters
I need to use a NSCharacterSet as an argument, but there are so many and I cannot determine what one will give me the same outcome as the previously used method.
An example URL I want to use will be like this
http://www.mapquestapi.com/geocoding/v1/batch?key=YOUR_KEY_HERE&callback=renderBatch&location=Pottsville,PA&location=Red Lion&location=19036&location=1090 N Charlotte St, Lancaster, PA
The URL Character Set for encoding seems to contain sets the trim my
URL. i.e,
The path component of a URL is the component immediately following the
host component (if present). It ends wherever the query or fragment
component begins. For example, in the URL
http://www.example.com/index.php?key1=value1, the path component is
/index.php.
However I don't want to trim any aspect of it.
When I used my String, for example myurlstring it would fail.
But when used the following, then there were no issues. It encoded the string with some magic and I could get my URL data.
let urlwithPercentEscapes = myurlstring.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
As it
Returns a representation of the String using a given encoding to
determine the percent escapes necessary to convert the String into a
legal URL string
Thanks
For the given URL string the equivalent to
let urlwithPercentEscapes = myurlstring.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
is the character set URLQueryAllowedCharacterSet
let urlwithPercentEscapes = myurlstring.stringByAddingPercentEncodingWithAllowedCharacters( NSCharacterSet.URLQueryAllowedCharacterSet())
Swift 3:
let urlwithPercentEscapes = myurlstring.addingPercentEncoding( withAllowedCharacters: .urlQueryAllowed)
It encodes everything after the question mark in the URL string.
Since the method stringByAddingPercentEncodingWithAllowedCharacters can return nil, use optional bindings as suggested in the answer of Leo Dabus.
It will depend on your url. If your url is a path you can use the character set
urlPathAllowed
let myFileString = "My File.txt"
if let urlwithPercentEscapes = myFileString.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) {
print(urlwithPercentEscapes) // "My%20File.txt"
}
Creating a Character Set for URL Encoding
urlFragmentAllowed
urlHostAllowed
urlPasswordAllowed
urlQueryAllowed
urlUserAllowed
You can create also your own url character set:
let myUrlString = "http://www.mapquestapi.com/geocoding/v1/batch?key=YOUR_KEY_HERE&callback=renderBatch&location=Pottsville,PA&location=Red Lion&location=19036&location=1090 N Charlotte St, Lancaster, PA"
let urlSet = CharacterSet.urlFragmentAllowed
.union(.urlHostAllowed)
.union(.urlPasswordAllowed)
.union(.urlQueryAllowed)
.union(.urlUserAllowed)
extension CharacterSet {
static let urlAllowed = CharacterSet.urlFragmentAllowed
.union(.urlHostAllowed)
.union(.urlPasswordAllowed)
.union(.urlQueryAllowed)
.union(.urlUserAllowed)
}
if let urlwithPercentEscapes = myUrlString.addingPercentEncoding(withAllowedCharacters: .urlAllowed) {
print(urlwithPercentEscapes) // "http://www.mapquestapi.com/geocoding/v1/batch?key=YOUR_KEY_HERE&callback=renderBatch&location=Pottsville,PA&location=Red%20Lion&location=19036&location=1090%20N%20Charlotte%20St,%20Lancaster,%20PA"
}
Another option is to use URLComponents to properly create your url
Swift 3.0 (From grokswift)
Creating URLs from strings is a minefield for bugs. Just miss a single / or accidentally URL encode the ? in a query and your API call will fail and your app wonβt have any data to display (or even crash if you didnβt anticipate that possibility). Since iOS 8 thereβs a better way to build URLs using NSURLComponents and NSURLQueryItems.
func createURLWithComponents() -> URL? {
var urlComponents = URLComponents()
urlComponents.scheme = "http"
urlComponents.host = "www.mapquestapi.com"
urlComponents.path = "/geocoding/v1/batch"
let key = URLQueryItem(name: "key", value: "YOUR_KEY_HERE")
let callback = URLQueryItem(name: "callback", value: "renderBatch")
let locationA = URLQueryItem(name: "location", value: "Pottsville,PA")
let locationB = URLQueryItem(name: "location", value: "Red Lion")
let locationC = URLQueryItem(name: "location", value: "19036")
let locationD = URLQueryItem(name: "location", value: "1090 N Charlotte St, Lancaster, PA")
urlComponents.queryItems = [key, callback, locationA, locationB, locationC, locationD]
return urlComponents.url
}
Below is the code to access url using guard statement.
guard let url = createURLWithComponents() else {
print("invalid URL")
return nil
}
print(url)
Output:
http://www.mapquestapi.com/geocoding/v1/batch?key=YOUR_KEY_HERE&callback=renderBatch&location=Pottsville,PA&location=Red%20Lion&location=19036&location=1090%20N%20Charlotte%20St,%20Lancaster,%20PA
In Swift 3.1, I am using something like the following:
let query = "param1=value1¶m2=" + valueToEncode.addingPercentEncoding(withAllowedCharacters: .alphanumeric)
It's safer than .urlQueryAllowed and the others, because it this will encode every characters other than A-Z, a-z and 0-9. This works better when the value you are encoding may use special characters like ?, &, =, + and spaces.
In my case where the last component was non latin characters I did the following in Swift 2.2:
extension String {
func encodeUTF8() -> String? {
//If I can create an NSURL out of the string nothing is wrong with it
if let _ = NSURL(string: self) {
return self
}
//Get the last component from the string this will return subSequence
let optionalLastComponent = self.characters.split { $0 == "/" }.last
if let lastComponent = optionalLastComponent {
//Get the string from the sub sequence by mapping the characters to [String] then reduce the array to String
let lastComponentAsString = lastComponent.map { String($0) }.reduce("", combine: +)
//Get the range of the last component
if let rangeOfLastComponent = self.rangeOfString(lastComponentAsString) {
//Get the string without its last component
let stringWithoutLastComponent = self.substringToIndex(rangeOfLastComponent.startIndex)
//Encode the last component
if let lastComponentEncoded = lastComponentAsString.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.alphanumericCharacterSet()) {
//Finally append the original string (without its last component) to the encoded part (encoded last component)
let encodedString = stringWithoutLastComponent + lastComponentEncoded
//Return the string (original string/encoded string)
return encodedString
}
}
}
return nil;
}
}
Swift 4.0
let encodedData = myUrlString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlHostAllowed)
I'm writing a mini-console of sorts and I'm trying to figure out how to extract things from a link. For example, in PHP this is a request variable
so:
http://somelink.com/somephp.php?variable1=10&variable2=20
Then PHP figures out the url parameters and assigns them to a variable.
How would I parse something like this in Swift?
So, given the string I'd want to take: variable1=10 and variable2=20 etc, is there a simple way to do this? I tried googling around but didn't really know what I was searching for.
I have a really horrible hacky way of doing this but it's not really extendable.
Youβd be wanting NSURLComponents:
import Foundation
let urlStr = "http://somelink.com/somephp.php?variable1=10&variable2=20"
let components = NSURLComponents(string: urlStr)
components?.queryItems?.first?.name // Optional("variable1")
components?.queryItems?.first?.value // Optional("10")
You might find it helpful to add a subscript operator for the query items:
extension NSURLComponents {
subscript(queryItemName: String) -> String? {
// of course, if you do this a lot,
// cache it in a dictionary instead
for item in self.queryItems ?? [] {
if item.name == queryItemName {
return item.value
}
}
return nil
}
}
if let components = NSURLComponents(string: urlStr) {
components["variable1"] ?? "No value"
}
I am looking for a way to replace a word inside a string in swift. Can anyone help?
this is what I have so far, I can find the specific word, but i do not know how to replace it...
var str = "helo, playgound"
var findWords = ["helo","playgound"]
var replaceWords = ["hello","playground"]
extension String {
var wordList:[String] {
return "".join(componentsSeparatedByCharactersInSet(NSCharacterSet.punctuationCharacterSet())).componentsSeparatedByString(" ")
}
}
func stringToArray() -> Array<String> {
var arr = str.wordList
return arr
}
func correction(var _arr:Array<String>) -> String{
for var i = 0; i < _arr.count; i++ {
if str.lowercaseString.rangeOfString(findWords[i]) != nil {
println("exists")
}
}
return str
}
It depends what your definition of a "word" is. If you're looking for an intelligent built-in notion of a "word", the easiest solution is probably to use NSRegularExpression, which knows where "word" boundaries are:
var s = NSMutableString(string:"hello world, go to hell")
let r = NSRegularExpression(
pattern: "\\bhell\\b",
options: .CaseInsensitive, error: nil)!
r.replaceMatchesInString(
s, options: nil, range: NSMakeRange(0,s.length),
withTemplate: "heaven")
After that, s is "hello world, go to heaven", which is the right answer; we replaced the "hell" that is a word, but not the "hell" in "hello". Notice that we are also matching case-insensitively, which seems to be one of your desiderata.
That example shows how do just one pair ("hell" and "heaven") but it is easy to abstract it into a method so that you can do it again and again for further pairs:
var str = "helo, playgound"
var findWords = ["helo", "playgound"]
var replaceWords = ["hello", "playground"]
func correct(str:String, orig:String, repl:String) -> String {
var s = NSMutableString(string:str)
let r = NSRegularExpression(
pattern: "\\b\(orig)\\b",
options: .CaseInsensitive, error: nil)!
r.replaceMatchesInString(
s, options: nil, range: NSMakeRange(0,s.length),
withTemplate: repl)
return s
}
for pair in Zip2(findWords,replaceWords) {
str = correct(str, pair.0, pair.1)
}
str // hello, playground
The easiest is probably this:
let statement = "Swift is hard."
let swiftRange = statement.startIndex..<advance(statement.startIndex, 5)
let newStatement = statement.stringByReplacingCharactersInRange(swiftRange, withString: "Objective-C")
// now newStatement = "Objective-C is hard."
Following a longer commenting tour: The above is under the assumption of the OP "I can find the specific word, but i do not know how to replace it...", so it's not about finding a "word" which to define is another discussion. It's just about replacing an already found word.
Another word on stringByReplacingCharactersInRange: #matt states that this is Cocoa cross-over. In that case Apple is telling a plain lie:
I fostered the web but there's no Apple source telling anything. Only the Foundation method for NSString. Their Swift book is silent too (in many respects). Well, I don't trust Apple anyway any longer since Yosemite-fail.