Skip to content

Commit

Permalink
Enhance attribute escaping.
Browse files Browse the repository at this point in the history
  • Loading branch information
netzpirat committed Aug 3, 2012
1 parent a613065 commit 6b5fcfe
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 19 deletions.
86 changes: 78 additions & 8 deletions lib/nodes/haml.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions spec/suites/templates/coffee/quotes.haml
Original file line number Diff line number Diff line change
@@ -1 +1,21 @@
#page{:style => 'font-family: "Lucida Grande", "Verdana"; text-align: center'}
#page{:style => "font-family: \"Lucida Grande\", \"Verdana\"; text-align: center"}
#page{:style => "font-family: 'Lucida Grande', 'Verdana'; text-align: center"}
#page{:style => 'font-family: \'Lucida Grande\', \'Verdana\'; text-align: center'}

%a{ href: '#', title: "This shouldn't be a double quote" } Works now
%a{ href: '#', title: 'Say "hi"' } To me

- name = 'Michael'
%div{ hi: name }
%div{ hi: "Hi #{ name }!" }

%div{ hi: "Hi #{ name }'!'" }
%div{ hi: "Hi #{ name }\"!\"" }

%div{ hi: "Hi #{ name } and #{ "'#{ name }'" }!" }

- theclass = 'test'
.clazz{ class: theclass }
.clazz{ class: 'hi' + theclass }
.clazz{ class: "hi#{ theclass }" }
15 changes: 14 additions & 1 deletion spec/suites/templates/coffee/quotes.html
Original file line number Diff line number Diff line change
@@ -1 +1,14 @@
<div id='page' style='font-family: "Lucida Grande", "Verdana"; text-align: center'></div>
<div id='page' style='font-family: "Lucida Grande", "Verdana"; text-align: center'></div>
<div id='page' style='font-family: "Lucida Grande", "Verdana"; text-align: center'></div>
<div id='page' style="font-family: 'Lucida Grande', 'Verdana'; text-align: center"></div>
<div id='page' style="font-family: 'Lucida Grande', 'Verdana'; text-align: center"></div>
<a href='#' title="This shouldn't be a double quote">Works now</a>
<a href='#' title='Say "hi"'>To me</a>
<div hi='Michael'></div>
<div hi='Hi Michael!'></div>
<div hi="Hi Michael'!'"></div>
<div hi='Hi Michael"!"'></div>
<div hi='Hi Michael and 'Michael'!'></div>
<div class='clazz test'></div>
<div class='clazz hitest'></div>
<div class='clazz hitest'></div>
2 changes: 1 addition & 1 deletion spec/suites/templates/filters/script_css.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@
/*]]>*/
</style>
</head>
<body onload='greet("hello")'>I'm Pink</body>
<body onload="greet('hello')">I'm Pink</body>
88 changes: 79 additions & 9 deletions src/nodes/haml.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -472,22 +472,92 @@ module.exports = class Haml extends Node
quoteAndEscapeAttributeValue: (value, code = false) ->
return unless value

value = quoted[2] if quoted = value.match /^("|')(.*)\1$/
value = quoted[2] if quoted = value.match /^("|')(.*)\1$/
tokens = @splitInterpolations value

hasSingleQuotes = false
hasDoubleQuotes = false
hasInterpolation = false

# Analyse existing quotes
for token in tokens
if token[0..1] is '#{'
hasInterpolation = true
else
hasSingleQuotes = token.indexOf("'") isnt -1 unless hasSingleQuotes
hasDoubleQuotes = token.indexOf('"') isnt -1 unless hasDoubleQuotes

# Our value is in a code block
if code
if value.indexOf('#{') is -1
result = "'#{ value }'"
if hasInterpolation
result = "\"#{ tokens.join('') }\""
else
result = "\"#{ value }\""
result = "'#{ tokens.join('') }'"

else
if value.indexOf('#{') is -1
result = "'#{ value.replace(/"/g, '\\\"').replace(/'/g, '\\\"') }'"
else
result = "'#{ value }'"
# Without any qoutes, wrap the value in single quotes
if not hasDoubleQuotes and not hasSingleQuotes
result = "'#{ tokens.join('') }'"

# With only single quotes, wrap the value in double quotes
if hasSingleQuotes and not hasDoubleQuotes
result = "\\\"#{ tokens.join('') }\\\""

# With only double quotes, wrap the value in single quotes and escape the double quotes
if hasDoubleQuotes and not hasSingleQuotes
escaped = for token in tokens
escapeQuotes(token)
result = "'#{ escaped.join('') }'"

# With both type of quotes, wrap the value in single quotes, escape the double quotes and
# convert the single quotes to it's entity representation
if hasSingleQuotes and hasDoubleQuotes
escaped = for token in tokens
escapeQuotes(token).replace(/'/g, '&#39;')
result = "'#{ escaped.join('') }'"

result

# Split expression by its interpolations.
#
# @example
# 'Hello #{ "#{ soso({}) }" } Interpol') => ["Hello ", "#{ "#{ soso({}) }" }", " Interpol"]
# 'Hello #{ "#{ soso }" } Interpol') => ["Hello ", "#{ "#{ soso }" }", " Interpol"]
# 'Hello #{ int } Interpol') => ["Hello ", "#{ int }", " Interpol"]
# 'Hello Interpol') => ["Hello Interpol"]
# '#{ int } Interpol') => ["#{ int }", " Interpol"]
# 'Hello #{ int }') => ["Hello ", "#{ int }"]
# '#{ int }') => ["#{ int }"]
#
# @param [String] value the attribute value
# @return [Array<String>] the splitted string
#
splitInterpolations: (value) ->
level = 0
start = 0
tokens = []
quoted = false

for pos in [0...value.length]
ch = value[pos]
ch2 = value[pos..pos + 1]

if ch is '{'
level += 1

if ch2 is '#{' and level is 0
tokens.push(value[start...pos])
start = pos

if ch is '}'
level -= 1

if level is 0
tokens.push(value[start..pos])
start = pos + 1

tokens.push(value[start...value.length])

tokens.filter(Boolean)

# Build the DocType string depending on the `!!!` token
# and the currently used HTML format.
Expand Down

0 comments on commit 6b5fcfe

Please sign in to comment.