A lot has been said in past years about gaps in punctuation documentation so I thought I’d share a bit of the code I needed to pass the punctuation_FullMontyQuotesIn test.
Not shown are the bits to shuffle around the closing quote, as indicated by the CSL, but that’s not hard.
I hope to come across the basis for these rules, to reference here, but for now it’s a bit arbitrary, just making the test pass.
// https://discourse.citationstyles.org/t/more-on-punctuation-notes/264
// https://discourse.citationstyles.org/t/english-vs-american-style-punctuation/269
// https://discourse.citationstyles.org/t/punctuation-for-quote-within-quote-where-punctuation-in-quote-true/1196
public extension Character {
/// Whether character is a punctuation mark
var isPunctuation: Bool { isQuotablePunctuation || ":;".contains(self) }
/// Whether character is punctuation that may be moved within an ending quotation
var isQuotablePunctuation: Bool { "!?.,".contains(self) }
/// Whether this character is gramatically invalid after another character
///
/// For example, two colons shouldn't appear together
func invalidAfter(_ before: Character?) -> Bool {
guard let before else { return false }
switch self {
case ":": return ":;!?".contains(before)
case ";": return ";".contains(before)
case ".": return ".:;!?".contains(before)
case ",": return ",".contains(before)
case "!": return "!:;".contains(before)
case "?": return "?:;".contains(before)
default: return false
}
}
/// If two characters are invalid together, per `invalidAfter()`, the *latter* character is
/// suppressed unless the first is `preferToRemove=true`, in which case *it* is suppressed
func preferToRemove(whenNextTo other: Character) -> Bool {
":;".contains(self) && "!?".contains(other)
}
}