From 27eb53b48cf027345e7ab6d6dc18f900a14fd418 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rainer=20Schl=C3=B6nvoigt?= Date: Thu, 22 Jun 2023 16:24:29 +0200 Subject: [PATCH] Exposing a rawLiteral on DocString for situation where sanitization is too eager --- .../CucumberSwift/Gherkin/Lexer/Lexer.swift | 27 ++++++++++++++----- .../Gherkin/Parser/DocString.swift | 1 + .../Gherkin/DocstringTests.swift | 26 ++++++++++++++++++ 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/Sources/CucumberSwift/Gherkin/Lexer/Lexer.swift b/Sources/CucumberSwift/Gherkin/Lexer/Lexer.swift index 3c7a31e6..104bd011 100644 --- a/Sources/CucumberSwift/Gherkin/Lexer/Lexer.swift +++ b/Sources/CucumberSwift/Gherkin/Lexer/Lexer.swift @@ -75,13 +75,18 @@ public class Lexer: StringReader { } } - @discardableResult internal func readDocString(_ evaluation: ((Character) -> Bool)) -> String { + @discardableResult internal func readDocString( + _ evaluation: ((Character) -> Bool) + ) -> (docString: String, rawDocString: String) { var str = "" + var rawStr = "" while let char = currentChar { if char.isEscapeCharacter, let next = nextChar, next.isDocStringLiteral { str.append(next) + rawStr.append(char) + rawStr.append(next) advanceIndex() advanceIndex() continue @@ -90,9 +95,10 @@ public class Lexer: StringReader { break } str.append(char) + rawStr.append(char) advanceIndex() } - return str + return (str, rawStr) } @discardableResult internal func stripSpaceIfNecessary() -> Bool { @@ -159,13 +165,16 @@ public class Lexer: StringReader { let open = lookAheadAtLineUntil { !$0.isDocStringLiteral } if open.isDocStringLiteral() { readLineUntil { !$0.isDocStringLiteral } - let docStringValues = readDocString { + + let (docString, rawDocString) = readDocString { if $0.isDocStringLiteral { let close = lookAheadAtLineUntil { !$0.isDocStringLiteral } if close == open { return true } } return false - }.components(separatedBy: "\n") + } + + let docStringValues = docString.components(separatedBy: "\n") .enumerated() .reduce(into: (whitespaceCount: 0, trimmedLines: [String]())) { res, e in let (offset, line) = e @@ -181,8 +190,14 @@ public class Lexer: StringReader { .dropLast { $0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty } readLineUntil { !$0.isDocStringLiteral } - return advance(.docString(position, DocString(literal: docStringValues.dropFirst().joined(separator: "\n"), - contentType: docStringValues.first?.trimmingCharacters(in: .whitespacesAndNewlines)))) + return advance(.docString( + position, + DocString( + rawLiteral: rawDocString, + literal: docStringValues.dropFirst().joined(separator: "\n"), + contentType: docStringValues.first?.trimmingCharacters(in: .whitespacesAndNewlines) + ) + )) } else if char == .quote { return advance(.match(position, "\(Character.quote)")) } diff --git a/Sources/CucumberSwift/Gherkin/Parser/DocString.swift b/Sources/CucumberSwift/Gherkin/Parser/DocString.swift index 8e4e4ab6..75c7a1a3 100644 --- a/Sources/CucumberSwift/Gherkin/Parser/DocString.swift +++ b/Sources/CucumberSwift/Gherkin/Parser/DocString.swift @@ -8,6 +8,7 @@ import Foundation public struct DocString: Hashable { + public let rawLiteral: String public var literal: String public var contentType: String? } diff --git a/Tests/CucumberSwiftTests/Gherkin/DocstringTests.swift b/Tests/CucumberSwiftTests/Gherkin/DocstringTests.swift index 10b4c8df..87262558 100644 --- a/Tests/CucumberSwiftTests/Gherkin/DocstringTests.swift +++ b/Tests/CucumberSwiftTests/Gherkin/DocstringTests.swift @@ -162,4 +162,30 @@ class DocstringTests: XCTestCase { third line """) } + + func testDocStringPreservesEscapedQuotesWithinJSONString() throws { + let cucumber = Cucumber(withString: + #""" + Feature: DocString variations + + Scenario: minimalistic + Given a DocString with JSON with escaped quotes in a string + """ + { + "stringWithEscapedQuote":"String with a \"quote\" or two" + } + """ + """# + ) + let firstStep = cucumber.features.first?.scenarios.first?.steps.first + let jsonString = try XCTUnwrap(firstStep?.docString?.rawLiteral) + let jsonData = try XCTUnwrap(jsonString.data(using: .utf8)) + + struct MyJsonType: Decodable { + let stringWithEscapedQuote: String + } + + let jsonObject = try JSONDecoder().decode(MyJsonType.self, from: jsonData) + XCTAssertEqual(jsonObject.stringWithEscapedQuote, "String with a \"quote\" or two") + } }