As said in comments, you currently have JSON (but you say to not focus on it, but...), which make the Regex construction quite hard.
As I suspect a Stream, I create fake tests values for that, but it's not necessary the case:
let startSeparator = "<OWS>{<OWS>"
let endSeparator = "<OWS>}<OWS>"
//Fake structure
struct Object: Codable {
let id: Int
let NDAHeader: Header
let ItemsSaleable: [Saleable]
}
struct Header: Codable {}
struct Saleable: Codable {}
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let str0 = embedStr(codable: Object(id: 0, NDAHeader: Header(), ItemsSaleable: []), with: encoder)
let str1 = embedStr(codable: Object(id: 1, NDAHeader: Header(), ItemsSaleable: []), with: encoder)
let str2 = embedStr(codable: Object(id: 2, NDAHeader: Header(), ItemsSaleable: []), with: encoder)
let str3 = embedStr(codable: Object(id: 3, NDAHeader: Header(), ItemsSaleable: []), with: encoder)
//Replace starting `{` & closing `}` of JSON with surroundnig <OWS>
func embedStr(codable: Codable, with encoder: JSONEncoder) -> String {
let jsonData = try! encoder.encode(codable)
var value = String(data: jsonData, encoding: .utf8)!
value = startSeparator + String(value.dropFirst())
value = String(value.dropLast()) + endSeparator
return value
}
//Create a fake stream, by joining multiple JSON values, and "cut it"
func concate(strs: [String], dropStart: Int, dropEnd: Int) -> String {
var value = strs.joined()
value = String(value.dropFirst(dropStart))
value = String(value.dropLast(dropEnd))
return value
}
//Fake Streams
let concate0 = concate(strs: [str0], dropStart: 0, dropEnd: 0)
let concate1 = concate(strs: [str0, str1, str2], dropStart: 13, dropEnd: 13)
let concate2 = concate(strs: [str0, str1, str2, str3], dropStart: 20, dropEnd: 13)
The "extract/find" code:
//Here, if it's a stream, you could return the rest of `value`, because it might be the start of a message, and to concatenate with the next part of the stream
//Side note, if it's a `Data`, `range(of:range:)` can be called on `Data`, avoiding you a strinigification if possible (like going back to JSON to remove the pretty printed format)
func analyze(str: String, found: ((String) -> Void)) {
var value = str
var start = value.range(of: startSeparator)
//Better coding might be applied, it's more a proof of concept, but you should be able to grasp the logic:
// Search for START to next END, return that captured part with closure `found`
// Keep searching for the rest of the string.
guard start != nil else { return }
var end = value.range(of: endSeparator, range: start!.upperBound..<value.endIndex)
while (start != nil && end != nil) {
let sub = value[start!.upperBound..<end!.lowerBound]
found("{" + String(sub) + "}") //Here is hard encoded the part surrounded by <OWS> tag
value = String(value[end!.upperBound...])
start = value.range(of: startSeparator)
if start != nil {
end = value.range(of: endSeparator, range: start!.upperBound..<value.endIndex)
} else {
end = nil
}
}
}
To test:
func test(str: String) {
print("In \(str.debugDescription)")
analyze(str: str) { match in
print("Found \(match.debugDescription)")
//The next part isn't beautiful, but it's one of the safest way to get rid of spaces/\n which are part of the pretty printed
let withouthPrettyPrintedData = try! (JSONSerialization.data(withJSONObject: try! JSONSerialization.jsonObject(with: Data(match.utf8))))
print("Cleaned: \(String(data: withouthPrettyPrintedData, encoding: .utf8)!.debugDescription)")
}
print("")
}
//Test the fake streams
[concate0, concate1, concate2].forEach {
test(str: $0)
}
I used debugDescription
in order to see in console the "\n"
.
Output:
$>In "<OWS>{<OWS>\n \"id\" : 0,\n \"ItemsSaleable\" : [\n\n ],\n \"NDAHeader\" : {\n\n }\n<OWS>}<OWS>"
$>Found "{\n \"id\" : 0,\n \"ItemsSaleable\" : [\n\n ],\n \"NDAHeader\" : {\n\n }\n}"
$>Cleaned: "{\"id\":0,\"ItemsSaleable\":[],\"NDAHeader\":{}}"
$>In " \"id\" : 0,\n \"ItemsSaleable\" : [\n\n ],\n \"NDAHeader\" : {\n\n }\n<OWS>}<OWS><OWS>{<OWS>\n \"id\" : 1,\n \"ItemsSaleable\" : [\n\n ],\n \"NDAHeader\" : {\n\n }\n<OWS>}<OWS><OWS>{<OWS>\n \"id\" : 2,\n \"ItemsSaleable\" : [\n\n ],\n \"NDAHeader\" : {\n\n "
$>Found "{\n \"id\" : 1,\n \"ItemsSaleable\" : [\n\n ],\n \"NDAHeader\" : {\n\n }\n}"
$>Cleaned: "{\"id\":1,\"ItemsSaleable\":[],\"NDAHeader\":{}}"
$>In " 0,\n \"ItemsSaleable\" : [\n\n ],\n \"NDAHeader\" : {\n\n }\n<OWS>}<OWS><OWS>{<OWS>\n \"id\" : 1,\n \"ItemsSaleable\" : [\n\n ],\n \"NDAHeader\" : {\n\n }\n<OWS>}<OWS><OWS>{<OWS>\n \"id\" : 2,\n \"ItemsSaleable\" : [\n\n ],\n \"NDAHeader\" : {\n\n }\n<OWS>}<OWS><OWS>{<OWS>\n \"id\" : 3,\n \"ItemsSaleable\" : [\n\n ],\n \"NDAHeader\" : {\n\n "
$>Found "{\n \"id\" : 1,\n \"ItemsSaleable\" : [\n\n ],\n \"NDAHeader\" : {\n\n }\n}"
$>Cleaned: "{\"id\":1,\"ItemsSaleable\":[],\"NDAHeader\":{}}"
$>Found "{\n \"id\" : 2,\n \"ItemsSaleable\" : [\n\n ],\n \"NDAHeader\" : {\n\n }\n}"
$>Cleaned: "{\"id\":2,\"ItemsSaleable\":[],\"NDAHeader\":{}}"