diff --git a/source/BaselineOfBuoy/BaselineOfBuoy.class.st b/source/BaselineOfBuoy/BaselineOfBuoy.class.st index e276369..d7dd924 100644 --- a/source/BaselineOfBuoy/BaselineOfBuoy.class.st +++ b/source/BaselineOfBuoy/BaselineOfBuoy.class.st @@ -185,12 +185,10 @@ BaselineOfBuoy >> baselineLocalization: spec [ spec requires: #( 'Buoy-Assertions' 'Buoy-Dynamic-Binding' 'Buoy-Metaprogramming-Extensions' ) ]; group: 'Deployment' with: 'Buoy-Localization'; - package: 'Buoy-Localization-Extensions' with: [ spec requires: 'Buoy-Localization' ]; - group: 'Deployment' with: 'Buoy-Localization-Extensions'; package: 'Buoy-Localization-Pharo-Extensions' with: [ spec requires: 'Buoy-Localization' ]; group: 'Deployment' with: 'Buoy-Localization-Pharo-Extensions'; package: 'Buoy-Localization-Tests' - with: [ spec requires: #( 'Buoy-Localization-Extensions' 'Dependent-SUnit-Extensions' ) ]; + with: [ spec requires: #( 'Buoy-Localization-Pharo-Extensions' 'Dependent-SUnit-Extensions' ) ]; group: 'Tests' with: 'Buoy-Localization-Tests' ] diff --git a/source/Buoy-Deprecated-V8/LanguageRange.extension.st b/source/Buoy-Deprecated-V8/LanguageRange.extension.st new file mode 100644 index 0000000..b813190 --- /dev/null +++ b/source/Buoy-Deprecated-V8/LanguageRange.extension.st @@ -0,0 +1,10 @@ +Extension { #name : 'LanguageRange' } + +{ #category : '*Buoy-Deprecated-V8' } +LanguageRange class >> from: aSubtagCollection [ + + self + deprecated: 'Use composedOf: instead' + transformWith: '`@receiver from: `@subtags' -> '`@receiver composedOf: `@subtags'. + ^ self composedOf: aSubtagCollection +] diff --git a/source/Buoy-Deprecated-V8/LanguageTag.extension.st b/source/Buoy-Deprecated-V8/LanguageTag.extension.st new file mode 100644 index 0000000..a078db3 --- /dev/null +++ b/source/Buoy-Deprecated-V8/LanguageTag.extension.st @@ -0,0 +1,11 @@ +Extension { #name : 'LanguageTag' } + +{ #category : '*Buoy-Deprecated-V8' } +LanguageTag class >> from: aSubtagCollection [ + + self + deprecated: 'Use composedOf: instead' + transformWith: '`@receiver from: `@subtags' -> '`@receiver composedOf: `@subtags'. + + ^ self composedOf: aSubtagCollection +] diff --git a/source/Buoy-Deprecated-V8/package.st b/source/Buoy-Deprecated-V8/package.st new file mode 100644 index 0000000..b7cf0b0 --- /dev/null +++ b/source/Buoy-Deprecated-V8/package.st @@ -0,0 +1 @@ +Package { #name : 'Buoy-Deprecated-V8' } diff --git a/source/Buoy-Localization-Extensions/String.extension.st b/source/Buoy-Localization-Extensions/String.extension.st deleted file mode 100644 index 0b95cea..0000000 --- a/source/Buoy-Localization-Extensions/String.extension.st +++ /dev/null @@ -1,55 +0,0 @@ -Extension { #name : 'String' } - -{ #category : '*Buoy-Localization-Extensions' } -String >> asLanguageRange [ - - ^ LanguageRange fromString: self -] - -{ #category : '*Buoy-Localization-Extensions' } -String >> asLanguageTag [ - - ^ LanguageTag fromString: self -] - -{ #category : '*Buoy-Localization-Extensions' } -String >> escaped [ - - ^ self species new: self size streamContents: [ :result | - | stream | - stream := self readStream. - [ stream atEnd ] whileFalse: [ StringEscapingRule escape: stream next on: result ] - ] -] - -{ #category : '*Buoy-Localization-Extensions' } -String >> localized [ - - ^ self localizedWithAll: #( ) -] - -{ #category : '*Buoy-Localization-Extensions' } -String >> localizedWithAll: collection [ - - ^ NaturalLanguageTranslator current localize: self withAll: collection to: CurrentLocale value -] - -{ #category : '*Buoy-Localization-Extensions' } -String >> unescaped [ - - ^ self species new: self size streamContents: [ :result | - | stream | - stream := self readStream. - [ stream atEnd ] whileFalse: [ - | currentChar | - currentChar := stream next. - currentChar == $\ - ifTrue: [ - stream atEnd - ifTrue: [ AssertionFailed signal: 'Missing escape sequence' ] - ifFalse: [ StringEscapingRule unescape: stream next from: stream on: result ] - ] - ifFalse: [ result nextPut: currentChar ] - ] - ] -] diff --git a/source/Buoy-Localization-Extensions/package.st b/source/Buoy-Localization-Extensions/package.st deleted file mode 100644 index 217b1a5..0000000 --- a/source/Buoy-Localization-Extensions/package.st +++ /dev/null @@ -1 +0,0 @@ -Package { #name : 'Buoy-Localization-Extensions' } diff --git a/source/Buoy-Localization-GS64-Extensions/CharacterCollection.extension.st b/source/Buoy-Localization-GS64-Extensions/CharacterCollection.extension.st index cdddc88..f251b9f 100644 --- a/source/Buoy-Localization-GS64-Extensions/CharacterCollection.extension.st +++ b/source/Buoy-Localization-GS64-Extensions/CharacterCollection.extension.st @@ -15,11 +15,7 @@ CharacterCollection >> asLanguageTag [ { #category : '*Buoy-Localization-GS64-Extensions' } CharacterCollection >> escaped [ - ^ self species new: self size streamContents: [ :result | - | stream | - stream := self readStream. - [ stream atEnd ] whileFalse: [ StringEscapingRule escape: stream next on: result ] - ] + ^ EscapingAlgorithm new escape: self ] { #category : '*Buoy-Localization-GS64-Extensions' } @@ -37,19 +33,5 @@ CharacterCollection >> localizedWithAll: collection [ { #category : '*Buoy-Localization-GS64-Extensions' } CharacterCollection >> unescaped [ - ^ self species new: self size streamContents: [ :result | - | stream | - stream := self readStream. - [ stream atEnd ] whileFalse: [ - | currentChar | - currentChar := stream next. - currentChar == $\ - ifTrue: [ - stream atEnd - ifTrue: [ AssertionFailed signal: 'Missing escape sequence' ] - ifFalse: [ StringEscapingRule unescape: stream next from: stream on: result ] - ] - ifFalse: [ result nextPut: currentChar ] - ] - ] + ^ EscapingAlgorithm new unescape: self ] diff --git a/source/Buoy-Localization-GS64-Extensions/NaturalLanguageTranslator.class.st b/source/Buoy-Localization-GS64-Extensions/NaturalLanguageTranslator.class.st index d43be68..a9439a1 100644 --- a/source/Buoy-Localization-GS64-Extensions/NaturalLanguageTranslator.class.st +++ b/source/Buoy-Localization-GS64-Extensions/NaturalLanguageTranslator.class.st @@ -1,14 +1,6 @@ " -A NaturalLanguageTranslator is a dummy translator. +A NaturalLanguageTranslator is a placeholder for a language translator. -The localization framework is found in the gettext package usually -overriding this class completely. - -As an alternative you can register a translator using - - NaturalLanguageTranslator current: myTranslator - -If this is done the messages will be dispatched to it " Class { #name : 'NaturalLanguageTranslator', diff --git a/source/Buoy-Localization-Pharo-Extensions/String.extension.st b/source/Buoy-Localization-Pharo-Extensions/String.extension.st new file mode 100644 index 0000000..b2e340f --- /dev/null +++ b/source/Buoy-Localization-Pharo-Extensions/String.extension.st @@ -0,0 +1,37 @@ +Extension { #name : 'String' } + +{ #category : '*Buoy-Localization-Pharo-Extensions' } +String >> asLanguageRange [ + + ^ LanguageRange fromString: self +] + +{ #category : '*Buoy-Localization-Pharo-Extensions' } +String >> asLanguageTag [ + + ^ LanguageTag fromString: self +] + +{ #category : '*Buoy-Localization-Pharo-Extensions' } +String >> escaped [ + + ^ EscapingAlgorithm new escape: self +] + +{ #category : '*Buoy-Localization-Pharo-Extensions' } +String >> localized [ + + ^ self localizedWithAll: #( ) +] + +{ #category : '*Buoy-Localization-Pharo-Extensions' } +String >> localizedWithAll: collection [ + + ^ NaturalLanguageTranslator current localize: self withAll: collection to: CurrentLocale value +] + +{ #category : '*Buoy-Localization-Pharo-Extensions' } +String >> unescaped [ + + ^ EscapingAlgorithm new unescape: self +] diff --git a/source/Buoy-Localization-Tests/LanguageRangeTest.class.st b/source/Buoy-Localization-Tests/LanguageRangeTest.class.st index c1c3f87..3285f77 100644 --- a/source/Buoy-Localization-Tests/LanguageRangeTest.class.st +++ b/source/Buoy-Localization-Tests/LanguageRangeTest.class.st @@ -36,51 +36,100 @@ LanguageRangeTest >> testAsLanguageRange [ self assert: '*' asLanguageRange equals: LanguageRange any; assert: LanguageRange any asLanguageRange equals: LanguageRange any; - assert: 'es-AR' asLanguageRange equals: ( LanguageRange from: #( 'es' 'AR' ) ) asLanguageRange + assert: 'es-AR' asLanguageRange + equals: ( LanguageRange composedOf: #( 'es' 'AR' ) ) asLanguageRange ] { #category : 'tests' } -LanguageRangeTest >> testComparison [ +LanguageRangeTest >> testCantCreateWhenLanguageCodeIsInvalid [ - | range | + self + should: [ LanguageRange fromString: 'e' ] + raise: InstanceCreationFailed + withMessageText: 'ISO 639 language codes must be 2 or 3 letters.'; + should: [ LanguageRange fromString: 'e2' ] + raise: InstanceCreationFailed + withMessageText: 'ISO 639 language codes must consist only of letters.' +] - range := LanguageRange from: #('en' 'US'). - self - assert: range equals: ( LanguageRange fromString: 'en-us' ); - assert: range hash equals: ( LanguageRange fromString: 'en-us' ) hash; - deny: range equals: LanguageRange any; - deny: range equals: ( LanguageRange from: #('en') ) +{ #category : 'tests' } +LanguageRangeTest >> testCantCreateWhenRegionIsInvalid [ + + self + should: [ LanguageRange fromString: 'en-A3' ] + raise: InstanceCreationFailed + withMessageText: 'Supported ISO 3166-1 codes must be 2 letters.' ] { #category : 'tests' } -LanguageRangeTest >> testCreation [ +LanguageRangeTest >> testCantCreateWhenScriptIsInvalid [ - | range | + self + should: [ LanguageRange fromString: 'en-L123' ] + raise: InstanceCreationFailed + withMessageText: 'ISO 15924 script codes must be 4 letters.' +] - range := LanguageRange from: #('en' 'Latn' 'us'). - self - assert: range subtags equals: #('en' 'Latn' 'US'); - assert: range printString equals: 'en-Latn-US' +{ #category : 'tests' } +LanguageRangeTest >> testCantCreateWithEmptyString [ + + self + should: [ LanguageRange fromString: '' ] + raise: InstanceCreationFailed + withMessageText: 'At least one sub tag is required.' ] { #category : 'tests' } -LanguageRangeTest >> testMatches [ +LanguageRangeTest >> testCantCreateWithoutSubtags [ - | range | + self + should: [ LanguageRange composedOf: #( ) ] + raise: InstanceCreationFailed + withMessageText: 'At least one sub tag is required.' +] - range := LanguageRange from: #('en' 'US'). - self - deny: ( range matches: 'en-Latn-US' asLanguageTag ); - assert: ( range matches: 'en-US' asLanguageTag ); - deny: ( range matches: 'en' asLanguageTag ); - assert: ( range matches: 'en-us-x-x-x' asLanguageTag ). +{ #category : 'tests' } +LanguageRangeTest >> testComparison [ - range := LanguageRange from: #('en'). - self - assert: ( range matches: 'en-Latn-US' asLanguageTag ); - assert: ( range matches: 'en-US' asLanguageTag ); - assert: ( range matches: 'en' asLanguageTag ); - assert: ( range matches: 'en-us-x-x-x' asLanguageTag ) + | range | + range := LanguageRange composedOf: #( 'en' 'US' ). + self + assert: range equals: ( LanguageRange fromString: 'en-us' ); + assert: range hash equals: ( LanguageRange fromString: 'en-us' ) hash; + deny: range equals: LanguageRange any; + deny: range equals: ( LanguageRange composedOf: #( 'en' ) ) +] + +{ #category : 'tests' } +LanguageRangeTest >> testCreation [ + + | range | + range := LanguageRange composedOf: #( 'en' 'Latn' 'us' ). + self + assert: range subtags equals: #( 'en' 'Latn' 'US' ); + assert: range printString equals: 'en-Latn-US' +] + +{ #category : 'tests' } +LanguageRangeTest >> testMatches [ + + | range | + range := LanguageRange composedOf: #( 'en' 'US' ). + self + deny: ( range matches: 'en-Latn-US' asLanguageTag ); + assert: ( range matches: 'en-US' asLanguageTag ); + deny: ( range matches: 'en' asLanguageTag ); + assert: ( range matches: 'en-us-x-x-x' asLanguageTag ); + deny: ( range matches: 'en-CA' asLanguageTag ). + + range := LanguageRange composedOf: #( 'en' ). + self + assert: ( range matches: 'en-Latn-US' asLanguageTag ); + assert: ( range matches: 'en-US' asLanguageTag ); + assert: ( range matches: 'en-CA' asLanguageTag ); + assert: ( range matches: 'en' asLanguageTag ); + assert: ( range matches: 'en-us-x-x-x' asLanguageTag ); + deny: ( range matches: 'es' asLanguageTag ) ] { #category : 'tests' } diff --git a/source/Buoy-Localization-Tests/LanguageTagTest.class.st b/source/Buoy-Localization-Tests/LanguageTagTest.class.st index f2fcad5..b1d6e05 100644 --- a/source/Buoy-Localization-Tests/LanguageTagTest.class.st +++ b/source/Buoy-Localization-Tests/LanguageTagTest.class.st @@ -38,15 +38,33 @@ LanguageTagTest >> testCantCreateWhenScriptIsInvalid [ withMessageText: 'ISO 15924 script codes must be 4 letters.' ] +{ #category : 'tests' } +LanguageTagTest >> testCantCreateWithEmptyString [ + + self + should: [ LanguageTag fromString: '' ] + raise: InstanceCreationFailed + withMessageText: 'At least one sub tag is required.' +] + +{ #category : 'tests' } +LanguageTagTest >> testCantCreateWithoutSubtags [ + + self + should: [ LanguageTag composedOf: #( ) ] + raise: InstanceCreationFailed + withMessageText: 'At least one sub tag is required.' +] + { #category : 'tests' } LanguageTagTest >> testComparison [ | tag | - tag := LanguageTag from: #( 'en' 'US' ). + tag := LanguageTag composedOf: #( 'en' 'US' ). self assert: tag equals: ( LanguageTag fromString: 'en-us' ); assert: tag hash equals: ( LanguageTag fromString: 'en-us' ) hash; - deny: tag equals: ( LanguageTag from: #( 'en' ) ) + deny: tag equals: ( LanguageTag composedOf: #( 'en' ) ) ] { #category : 'tests' } @@ -139,7 +157,7 @@ LanguageTagTest >> testCreationWithScriptButNoRegion [ LanguageTagTest >> testPrintString [ | tag | - tag := LanguageTag from: #( 'en' 'US' ). + tag := LanguageTag composedOf: #( 'en' 'US' ). self assert: tag asString equals: 'en-US'; assert: tag printString equals: 'en-US' diff --git a/source/Buoy-Localization-Tests/StringLocalizationExtensionsTest.class.st b/source/Buoy-Localization-Tests/StringLocalizationExtensionsTest.class.st index a560fc5..0b95b1b 100644 --- a/source/Buoy-Localization-Tests/StringLocalizationExtensionsTest.class.st +++ b/source/Buoy-Localization-Tests/StringLocalizationExtensionsTest.class.st @@ -12,7 +12,7 @@ StringLocalizationExtensionsTest >> assertUnescaping: string raisesAsError: erro ] { #category : 'tests - escaping' } -StringLocalizationExtensionsTest >> testErrorsInUnescaped [ +StringLocalizationExtensionsTest >> testErrorsWhenUnescaping [ self assertUnescaping: '\💩' raisesAsError: 'There''s no escaping rule for "💩" (\u{1F4A9})'. @@ -89,7 +89,7 @@ StringLocalizationExtensionsTest >> testEscapedUnicode [ ] { #category : 'tests - escaping' } -StringLocalizationExtensionsTest >> testEscapedWithoutEscapingSequence [ +StringLocalizationExtensionsTest >> testEscapedWhenThereIsNoEscapingSequence [ self assert: 'aabb' escaped equals: 'aabb'; @@ -236,7 +236,7 @@ StringLocalizationExtensionsTest >> testUnescapedUnicode [ ] { #category : 'tests - escaping' } -StringLocalizationExtensionsTest >> testUnescapedWithoutEscapingSequence [ +StringLocalizationExtensionsTest >> testUnescapedWhenThereIsNoEscapingSequence [ self assert: 'aabb' unescaped equals: 'aabb'; diff --git a/source/Buoy-Localization/ControlCharactersEscapingRule.class.st b/source/Buoy-Localization/ControlCharactersEscapingRule.class.st index 8dd3904..8e1de16 100644 --- a/source/Buoy-Localization/ControlCharactersEscapingRule.class.st +++ b/source/Buoy-Localization/ControlCharactersEscapingRule.class.st @@ -62,7 +62,7 @@ ControlCharactersEscapingRule >> priority [ ControlCharactersEscapingRule >> raiseError [ - self error: 'The control character escaping rule is mishandling some case' + self error: 'The control character escaping rule is mishandling some case' ] { #category : 'escaping' } diff --git a/source/Buoy-Localization/EscapingAlgorithm.class.st b/source/Buoy-Localization/EscapingAlgorithm.class.st new file mode 100644 index 0000000..7dc4e1e --- /dev/null +++ b/source/Buoy-Localization/EscapingAlgorithm.class.st @@ -0,0 +1,52 @@ +Class { + #name : 'EscapingAlgorithm', + #superclass : 'Object', + #classInstVars : [ + 'UniqueInstace' + ], + #category : 'Buoy-Localization', + #package : 'Buoy-Localization' +} + +{ #category : 'class initialization' } +EscapingAlgorithm class >> initialize [ + + + UniqueInstace := super new +] + +{ #category : 'instance creation' } +EscapingAlgorithm class >> new [ + + ^ UniqueInstace +] + +{ #category : 'escaping' } +EscapingAlgorithm >> escape: string [ + + ^ string species new: string size streamContents: [ :result | + | stream | + stream := string readStream. + [ stream atEnd ] whileFalse: [ StringEscapingRule escape: stream next on: result ] + ] +] + +{ #category : 'escaping' } +EscapingAlgorithm >> unescape: string [ + + ^ string species new: string size streamContents: [ :result | + | stream | + stream := string readStream. + [ stream atEnd ] whileFalse: [ + | currentChar | + currentChar := stream next. + currentChar == $\ + ifTrue: [ + stream atEnd + ifTrue: [ AssertionFailed signal: 'Missing escape sequence' ] + ifFalse: [ StringEscapingRule unescape: stream next from: stream on: result ] + ] + ifFalse: [ result nextPut: currentChar ] + ] + ] +] diff --git a/source/Buoy-Localization/LanguageRange.class.st b/source/Buoy-Localization/LanguageRange.class.st index f53a3b1..f0d99d1 100644 --- a/source/Buoy-Localization/LanguageRange.class.st +++ b/source/Buoy-Localization/LanguageRange.class.st @@ -28,13 +28,13 @@ Class { { #category : 'instance creation' } LanguageRange class >> any [ - ^ self with: Optional unused + ^ self matchUsing: Optional unused ] { #category : 'instance creation' } -LanguageRange class >> from: aSubtagCollection [ +LanguageRange class >> composedOf: aSubtagCollection [ - ^ self with: ( Optional containing: ( LanguageTag from: aSubtagCollection ) ) + ^ self matchUsing: ( Optional containing: ( LanguageTag composedOf: aSubtagCollection ) ) ] { #category : 'instance creation' } @@ -42,13 +42,13 @@ LanguageRange class >> fromString: aString [ ^ aString = '*' then: [ self any ] - otherwise: [ self with: ( Optional containing: ( LanguageTag fromString: aString ) ) ] + otherwise: [ self matchUsing: ( Optional containing: ( LanguageTag fromString: aString ) ) ] ] { #category : 'private - instance creation' } -LanguageRange class >> with: aLanguageTagOptional [ +LanguageRange class >> matchUsing: aLanguageTagOptional [ - ^ self new initializeWith: aLanguageTagOptional + ^ self new initializeToMatchUsing: aLanguageTagOptional ] { #category : 'comparing' } @@ -78,7 +78,7 @@ LanguageRange >> hash [ ] { #category : 'initialization' } -LanguageRange >> initializeWith: aLanguageTagOptional [ +LanguageRange >> initializeToMatchUsing: aLanguageTagOptional [ languageTagOptional := aLanguageTagOptional ] diff --git a/source/Buoy-Localization/LanguageTag.class.st b/source/Buoy-Localization/LanguageTag.class.st index e84fd8b..3de66b8 100644 --- a/source/Buoy-Localization/LanguageTag.class.st +++ b/source/Buoy-Localization/LanguageTag.class.st @@ -27,20 +27,20 @@ Class { } { #category : 'instance creation' } -LanguageTag class >> from: aSubtagCollection [ +LanguageTag class >> composedOf: aSubtagCollection [ AssertionChecker enforce: [ aSubtagCollection notEmpty ] - because: 'At least a sub tag is required.' + because: 'At least one sub tag is required.' raising: InstanceCreationFailed. - ^ self new initializeFrom: aSubtagCollection + ^ self new initializeComposedOf: aSubtagCollection ] { #category : 'instance creation' } LanguageTag class >> fromString: aString [ - ^ self from: ( aString substrings: '-' ) + ^ self composedOf: ( aString substrings: '-' ) ] { #category : 'comparing' } @@ -98,7 +98,7 @@ LanguageTag >> hash [ ] { #category : 'initialization' } -LanguageTag >> initializeFrom: aSubtagCollection [ +LanguageTag >> initializeComposedOf: aSubtagCollection [ subtags := Array withAll: aSubtagCollection. self @@ -140,16 +140,16 @@ LanguageTag >> initializeRegionTaggedAt: index [ { #category : 'private' } LanguageTag >> initializeScript [ - scriptOptional := Optional unused. + scriptOptional := Optional unused. - [ - | scriptCandidate | - scriptCandidate := subtags second asLowercase capitalized. - scriptCandidate size = 4 then: [ - subtags at: 2 put: scriptCandidate. - scriptOptional := Optional containing: scriptCandidate - ] - ] unless: subtags size = 1 + subtags size > 1 then: [ + | scriptCandidate | + scriptCandidate := subtags second asLowercase capitalized. + scriptCandidate size = 4 then: [ + subtags at: 2 put: scriptCandidate. + scriptOptional := Optional containing: scriptCandidate + ] + ] ] { #category : 'accessing' } diff --git a/source/Buoy-Localization/StringEscapingRule.class.st b/source/Buoy-Localization/StringEscapingRule.class.st index 7d61be5..9be88a4 100644 --- a/source/Buoy-Localization/StringEscapingRule.class.st +++ b/source/Buoy-Localization/StringEscapingRule.class.st @@ -8,29 +8,23 @@ Class { #package : 'Buoy-Localization' } +{ #category : 'private' } +StringEscapingRule class >> availableRules [ + + + Available ifNil: [ Available := SortedCollection sortBlock: [ :a :b | a priority > b priority ] ]. + ^ Available +] + { #category : 'escaping' } StringEscapingRule class >> escape: character on: targetStream [ - ^ Available + ^ self availableRules detect: [ :rule | rule handlesEscapeOf: character ] ifFound: [ :rule | rule escape: character on: targetStream ] ifNone: [ targetStream nextPut: character ] ] -{ #category : 'class initialization' } -StringEscapingRule class >> initialize [ - - - self initializeAvailableRules -] - -{ #category : 'class initialization' } -StringEscapingRule class >> initializeAvailableRules [ - - - Available ifNil: [ Available := SortedCollection sortBlock: [ :a :b | a priority > b priority ] ] -] - { #category : 'testing' } StringEscapingRule class >> isAbstract [ @@ -41,14 +35,13 @@ StringEscapingRule class >> isAbstract [ StringEscapingRule class >> registerRule: anEscapingRule [ - self initializeAvailableRules. - Available add: anEscapingRule + self availableRules add: anEscapingRule ] { #category : 'escaping' } StringEscapingRule class >> unescape: aCharacter from: sourceStream on: targetStream [ - ^ Available + ^ self availableRules detect: [ :rule | rule handlesUnescapeOf: aCharacter ] ifFound: [ :rule | rule unescape: aCharacter from: sourceStream on: targetStream ] ifNone: [