Skip to content

Commit

Permalink
Improve boolean conversions. (Closes #29)
Browse files Browse the repository at this point in the history
Prior to this commit, code like

    %param{ :value => 'false' }
    %param{ :value => 'true' }

 would be treated like boolean values, but they should be
 passed unmodified. This behaviour was changed in the
 HAML compiler, but leads to a further change:

 The boolean logic conversion at runtime, wasn't able to
 distinguish between real booleans and boolean like text
 attributes. The workaround to this is to let the clean function
 (`$c`) mark real boolean values with a hidden unicode marker
 character.
  • Loading branch information
netzpirat committed Jun 12, 2012
1 parent b57b340 commit 691c6bd
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 87 deletions.
5 changes: 4 additions & 1 deletion README.md
Expand Up @@ -493,7 +493,10 @@ since there is no Haml markup for this to instruct the compiler to change the es
* Default: `true`

Every output that is generated from evaluating CoffeeScript code is cleaned before inserting into the document. The
default implementation converts `null` or `undefined` values into an empty string.
default implementation converts `null` or `undefined` values into an empty string and marks real boolean values with a
hidden marker character. The hidden marker character is necessary to distinguish between String values like `'true'`,
`'false'` and real boolean values `true`, `false` in the markup, so that a boolean attribute conversion can quickly
convert these values to the correct HTML5/XHTML/HTML4 representation.

#### Preserve whitespace tags

Expand Down
10 changes: 7 additions & 3 deletions lib/haml-coffee.js

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

76 changes: 42 additions & 34 deletions lib/nodes/haml.js

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

8 changes: 4 additions & 4 deletions spec/suites/templates/text/boolean_attributes_html5.html
@@ -1,11 +1,11 @@
<input checked>
<input checked>
<input>
<input checked='true'>
<input>
<input checked='false'>
<input checked>
<input checked>
<input>
<input checked='true'>
<input>
<input checked='false'>
<tag name='value' name2 tag='100'></tag>
<tag name='value' name2>
<tag name='value' name2 ns:tag='100'></tag>
Expand Down
8 changes: 4 additions & 4 deletions spec/suites/templates/text/boolean_attributes_xhtml.html
@@ -1,11 +1,11 @@
<input checked='checked' />
<input checked='checked' />
<input />
<input checked='true' />
<input />
<input checked='false' />
<input checked='checked' />
<input checked='checked' />
<input />
<input checked='true' />
<input />
<input checked='false' />
<tag name='value' name2='name2' tag='100'></tag>
<tag name='value' name2='name2' />
<tag name='value' name2='name2' ns:tag='100'></tag>
Expand Down
16 changes: 12 additions & 4 deletions src/haml-coffee.coffee
Expand Up @@ -343,7 +343,11 @@ module.exports = class HamlCoffee
if @options.customCleanValue
fn += "$c = #{ @options.customCleanValue }\n"
else
fn += "$c = (text) -> if text is null or text is undefined then '' else text\n"
fn += "$c = (text) ->\n"
fn += " switch text\n"
fn += " when null, undefined then ''\n"
fn += " when true, false then '\u0093' + text"
fn += " else text\n"

# Preserve whitespace
if code.indexOf('$p') isnt -1 || code.indexOf('$fp') isnt -1
Expand Down Expand Up @@ -466,7 +470,11 @@ module.exports = class HamlCoffee
combined

# Adds a boolean convert logic that changes boolean attribute
# values depending on the output format.
# values depending on the output format. This works only when
# the clean value function add a hint marker (\u0093) to each
# boolean value, so that the conversion logic can disinguish
# between dynamic, real boolean values and string values like
# 'false' and 'true' or compile time attributes.
#
# With the XHTML format, an attribute `checked='true'` will be
# converted to `checked='checked'` and `checked='false'` will
Expand All @@ -480,9 +488,9 @@ module.exports = class HamlCoffee
#
convertBooleans: (code) ->
if @options.format is 'xhtml'
'.replace(/\\s(\\w+)=\'true\'/mg, " $1=\'$1\'").replace(/\\s(\\w+)=\'false\'/mg, \'\')'
'.replace(/\\s(\\w+)=\'\u0093true\'/mg, " $1=\'$1\'").replace(/\\s(\\w+)=\'\u0093false\'/mg, \'\')'
else
'.replace(/\\s(\\w+)=\'true\'/mg, \' $1\').replace(/\\s(\\w+)=\'false\'/mg, \'\')'
'.replace(/\\s(\\w+)=\'\u0093true\'/mg, \' $1\').replace(/\\s(\\w+)=\'\u0093false\'/mg, \'\')'

# Adds whitespace cleanup function when needed by the
# template. The cleanup must be done AFTER the template
Expand Down
84 changes: 47 additions & 37 deletions src/nodes/haml.coffee
Expand Up @@ -264,41 +264,37 @@ module.exports = class Haml extends Node

# Prepare all attributes
for key, value of @extractAttributes(exp)
bool = false

# Ignore attributes some attribute values
if ['false', ''].indexOf(value) is -1

# Set key to value if the value is boolean true
if ['true'].indexOf(value) isnt -1
value = "'#{ key }'"
bool = true

# Wrap plain attributes into an interpolation, expect boolean values
else if not value.match /^("|').*\1$/
if @escapeAttributes
if @cleanValue
value = '\'#{ $e($c(' + value + ')) }\''
else
value = '\'#{ $e(' + value + ') }\''
bool = false

# Handle boolean values
if value is 'true' or value is 'false'
bool = true

# Wrap plain attributes into an interpolation, expect boolean values
else if not value.match /^("|').*\1$/
if @escapeAttributes
if @cleanValue
value = '\'#{ $e($c(' + value + ')) }\''
else
if @cleanValue
value = '\'#{ $c(' + value + ') }\''
else
value = '\'#{ (' + value + ') }\''

# Unwrap value from quotes
value = quoted[2] if quoted = value.match /^("|')(.*)\1$/
value = '\'#{ $e(' + value + ') }\''
else
if @cleanValue
value = '\'#{ $c(' + value + ') }\''
else
value = '\'#{ (' + value + ') }\''

# Unwrap key from quotes
key = quoted[2] if quoted = key.match /^("|')(.*)\1$/
# Unwrap value from quotes
value = quoted[2] if quoted = value.match /^("|')(.*)\1$/

attributes.push {
key : key
value : value
bool : bool
}
# Unwrap key from quotes
key = quoted[2] if quoted = key.match /^("|')(.*)\1$/

attributes.push {
key : key
value : value
bool : bool
}

attributes

# Extracts the attributes from the expression.
Expand Down Expand Up @@ -351,7 +347,8 @@ module.exports = class Haml extends Node
# Split into key value pairs
pairs = exp.split(keys).filter(Boolean)

dataAttribute = false
inDataAttribute = false
hasDataAttribute = false

# Process the pairs in a group of two
while pairs.length
Expand All @@ -365,19 +362,22 @@ module.exports = class Haml extends Node
value = keyValue[1]?.replace(/^\s+|[\s,]+$/g, '').replace(/\u0090/, '')

if key is 'data'
dataAttribute = true
inDataAttribute = true
hasDataAttribute = true

else if key and value
if dataAttribute
if inDataAttribute
key = "data-#{ key }"
dataAttribute = false if /}\s*$/.test value
inDataAttribute = false if /}\s*$/.test value

switch type
when '('
attributes[key] = value.replace(/^\s+|[\s)]+$/g, '')
when '{'
attributes[key] = value.replace(/^\s+|[\s}]+$/g, '')

delete attributes['data'] if hasDataAttribute

attributes

# Build the HTML tag prefix by concatenating all the
Expand Down Expand Up @@ -424,8 +424,18 @@ module.exports = class Haml extends Node
# Construct tag attributes
if tokens.attributes
for attribute in tokens.attributes
if attribute.bool and @format is 'html5'
tagParts.push "#{ attribute.key }"

# Boolean attribute logic
if attribute.bool

# Only show true values
if attribute.value is 'true'
if @format is 'html5'
tagParts.push "#{ attribute.key }"
else
tagParts.push "#{ attribute.key }=#{ @quoteAttributeValue(attribute.key) }"

# Anything but booleans
else
tagParts.push "#{ attribute.key }=#{ @quoteAttributeValue(attribute.value) }"

Expand Down

0 comments on commit 691c6bd

Please sign in to comment.