@@ -4,11 +4,21 @@ import config from './config'
44import * as string from './util/string'
55import * as nodeType from './node-type'
66
7- let allowedElements , requiredAttributes , transformElements , blockLevelElements
7+ let allowedElements , requiredAttributes , transformElements , blockLevelElements , replaceQuotes
88let splitIntoBlocks , blacklistedElements
99const whitespaceOnly = / ^ \s * $ /
1010const blockPlaceholder = '<!-- BLOCK -->'
1111let keepInternalRelativeLinks
12+ const doubleQuotePairs = [
13+ [ '«' , '»' ] , // ch german, french
14+ [ '»' , '«' ] , // danish
15+ [ '"' , '"' ] , // danish, not specified
16+ [ '“' , '”' ] , // english US
17+ [ '”' , '”' ] , // swedish
18+ [ '“' , '“' ] , // chinese simplified
19+ [ '„' , '“' ] // german
20+ ]
21+ const quotesRegex = / ( [ " ' « » „ “ ] ) (? ! [ ^ < ] * ?> ) / g
1222
1323updateConfig ( config )
1424export function updateConfig ( conf ) {
@@ -18,6 +28,7 @@ export function updateConfig (conf) {
1828 transformElements = rules . transformElements || { }
1929 blacklistedElements = rules . blacklistedElements || [ ]
2030 keepInternalRelativeLinks = rules . keepInternalRelativeLinks || false
31+ replaceQuotes = rules . replaceQuotes || { }
2132
2233 blockLevelElements = { }
2334 rules . blockLevelElements . forEach ( ( name ) => { blockLevelElements [ name ] = true } )
@@ -80,7 +91,7 @@ export function parseContent (element) {
8091 return filterHtmlElements ( element )
8192 // Handle Blocks
8293 . split ( blockPlaceholder )
83- . map ( ( entry ) => string . trim ( cleanWhitespace ( entry ) ) )
94+ . map ( ( entry ) => string . trim ( cleanWhitespace ( replaceAllQuotes ( entry ) ) ) )
8495 . filter ( ( entry ) => ! whitespaceOnly . test ( entry ) )
8596}
8697
@@ -168,3 +179,69 @@ export function cleanWhitespace (str) {
168179 : ' '
169180 ) )
170181}
182+
183+ export function replaceAllQuotes ( str ) {
184+ const quotes = getAllQuotes ( str )
185+ if ( quotes && quotes . length > 0 ) {
186+ const replacementQuotes = getReplacementArray ( quotes , 0 )
187+ return replaceExistingQuotes ( str , replacementQuotes )
188+ }
189+ return str
190+ }
191+
192+ function getReplacementArray ( quotes , position ) {
193+ const quotesArray = [ ]
194+ if ( quotes . length === 1 ) {
195+ return quotes
196+ }
197+
198+ while ( position < quotes . length ) {
199+ const closingTagPosition = findClosingQuote ( quotes , position )
200+ let nestedArray = [ ]
201+
202+ if ( closingTagPosition !== position + 1 && closingTagPosition !== - 1 && closingTagPosition !== undefined ) {
203+ const nestedquotes = quotes . slice ( position + 1 , closingTagPosition )
204+ if ( nestedquotes ) {
205+ nestedArray = getReplacementArray ( nestedquotes , 0 )
206+ }
207+ }
208+ if ( closingTagPosition ) {
209+ position = closingTagPosition + 1
210+ }
211+ if ( closingTagPosition === undefined || closingTagPosition === - 1 ) {
212+ quotesArray . push ( quotes [ position ] )
213+ position ++
214+ } else {
215+ quotesArray . push ( ...[ replaceQuotes . quotes [ 0 ] , ...nestedArray , replaceQuotes . quotes [ 1 ] ] )
216+ }
217+ }
218+
219+ return quotesArray
220+ }
221+
222+ function findClosingQuote ( quotes , position ) {
223+ const openingQuote = quotes [ position ]
224+ for ( let i = position + 1 ; i < quotes . length ; i ++ ) {
225+ const isIncluded = getPossibleClosingQuotes ( openingQuote ) . includes ( quotes [ i ] )
226+ if ( isIncluded ) {
227+ return i
228+ }
229+ }
230+ }
231+
232+ function getPossibleClosingQuotes ( openingQuote ) {
233+ return doubleQuotePairs . filter ( quotePair => quotePair [ 0 ] === openingQuote ) . map ( ( quotePair ) => quotePair [ 1 ] )
234+ }
235+
236+ function getAllQuotes ( str ) {
237+ return str . match ( quotesRegex )
238+ }
239+
240+ function replaceExistingQuotes ( str , replacementQuotes ) {
241+ let index = 0
242+ return str . replace ( quotesRegex , ( match ) => {
243+ const replacement = replacementQuotes [ index ]
244+ index ++
245+ return replacement
246+ } )
247+ }
0 commit comments