Skip to content

Commit

Permalink
Working on default attribute values
Browse files Browse the repository at this point in the history
  • Loading branch information
sergeche committed Oct 29, 2013
1 parent ada5b85 commit ee1b56c
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 40 deletions.
1 change: 0 additions & 1 deletion .gitignore
Expand Up @@ -10,5 +10,4 @@
.DS_Store .DS_Store
/bower_components/ /bower_components/
/node_modules/ /node_modules/
/emmet.sublime-project
/emmet.sublime-workspace /emmet.sublime-workspace
18 changes: 18 additions & 0 deletions emmet.sublime-project
@@ -0,0 +1,18 @@
{
"folders":
[
{
"follow_symlinks": true,
"path": "lib"
}
],

"ternjs": {
"include": ["/lib/**/*.js"],
"disable_completions": ["**/parser/abbreviation.js"],
"plugins": {
"node": {},
"doc_comment": {}
}
}
}
129 changes: 90 additions & 39 deletions lib/parser/abbreviation.js
Expand Up @@ -31,9 +31,11 @@ define(function(require, exports, module) {
var procPastedContent = require('./processor/pastedContent'); var procPastedContent = require('./processor/pastedContent');
var procTagName = require('./processor/tagName'); var procTagName = require('./processor/tagName');
var procResourceMatcher = require('./processor/resourceMatcher'); var procResourceMatcher = require('./processor/resourceMatcher');
var procAttributes = require('./processor/attributes');


var reValidName = /^[\w\-\$\:@\!%]+\+?$/i; var reValidName = /^[\w\-\$\:@\!%]+\+?$/i;
var reWord = /[\w\-:\$@]/; var reWord = /[\w\-:\$@]/;
var DEFAULT_ATTR_NAME = '%default';


var pairs = { var pairs = {
'[': ']', '[': ']',
Expand Down Expand Up @@ -173,22 +175,7 @@ define(function(require, exports, module) {
* @returns {AbbreviationNode} * @returns {AbbreviationNode}
*/ */
find: function(fn) { find: function(fn) {
return this.findAll(fn)[0]; return this.findAll(fn, {amount: 1})[0];
// if (!_.isFunction(fn)) {
// var elemName = fn.toLowerCase();
// fn = function(item) {return item.name().toLowerCase() == elemName;};
// }
//
// var result = null;
// _.find(this.children, function(child) {
// if (fn(child)) {
// return result = child;
// }
//
// return result = child.find(fn);
// });
//
// return result;
}, },


/** /**
Expand All @@ -197,16 +184,23 @@ define(function(require, exports, module) {
* @param {Function} fn * @param {Function} fn
* @returns {Array} * @returns {Array}
*/ */
findAll: function(fn) { findAll: function(fn, state) {
state = _.extend({amount: 0, found: 0}, state || {});

if (!_.isFunction(fn)) { if (!_.isFunction(fn)) {
var elemName = fn.toLowerCase(); var elemName = fn.toLowerCase();
fn = function(item) {return item.name().toLowerCase() == elemName;}; fn = function(item) {return item.name().toLowerCase() == elemName;};
} }


var result = []; var result = [];
_.each(this.children, function(child) { _.each(this.children, function(child) {
if (fn(child)) if (fn(child)) {
result.push(child); result.push(child);
state.found++;
if (state.amount && state.found >= state.amount) {
return;
}
}


result = result.concat(child.findAll(fn)); result = result.concat(child.findAll(fn));
}); });
Expand Down Expand Up @@ -270,11 +264,26 @@ define(function(require, exports, module) {
/** /**
* Returns or sets attribute value * Returns or sets attribute value
* @param {String} name Attribute name * @param {String} name Attribute name
* @param {String} value New attribute value * @param {String} value New attribute value. `Null` value
* will remove attribute
* @returns {String} * @returns {String}
*/ */
attribute: function(name, value) { attribute: function(name, value) {
if (arguments.length == 2) { if (arguments.length == 2) {
if (value === null) {
// removing attribute
var vals = _.filter(this._attributes, function(attr) {
return attr.name === name;
});

var that = this;
_.each(vals, function(attr) {
that._attributes = _.without(that._attributes, attr);
});

return;
}

// modifying attribute // modifying attribute
var ix = _.indexOf(_.pluck(this._attributes, 'name'), name.toLowerCase()); var ix = _.indexOf(_.pluck(this._attributes, 'name'), name.toLowerCase());
if (~ix) { if (~ix) {
Expand Down Expand Up @@ -580,45 +589,84 @@ define(function(require, exports, module) {


return root; return root;
} }

/**
* If next character in stream is a quote, consumes and returns
* quoted string, returns `null` otherwise. If it fails to correctly
* consume quoted value (e.g. quoted value exists, but incorrectly
* written), returns `false`
* @param {StringStream} stream
* @return {String}
*/
function eatQuotedString(stream) {
var quote = stream.peek();
if (quote == '"' || quote == "'") {
stream.next();
if (consumeQuotedValue(stream, quote)) {
var out = stream.current();
return out.substring(1, out.length - 1);
} else {
return false;
}
}

return null;
}


/** /**
* Extract attributes and their values from attribute set: * Extract attributes and their values from attribute set:
* <code>[attr col=3 title="Quoted string"]</code> * <code>[attr col=3 title="Quoted string"]</code> (without square braces)
* @param {String} attrSet * @param {String} attrSet
* @returns {Array} * @returns {Array}
*/ */
function extractAttributes(attrSet, attrs) { function extractAttributes(attrSet) {
attrSet = utils.trim(attrSet); attrSet = utils.trim(attrSet);
var result = []; var result = [];

var attrName, attrValue, quote, nextChar;
/** @type StringStream */
var stream = stringStream.create(attrSet); var stream = stringStream.create(attrSet);
stream.eatSpace(); stream.eatSpace();


while (!stream.eol()) { while (!stream.eol()) {
stream.start = stream.pos; stream.start = stream.pos;

// look-up for quoted value, which is a value for default attribute
attrValue = eatQuotedString(stream);
if (attrValue) {
result.push({
name: DEFAULT_ATTR_NAME,
value: attrValue
});
stream.eatSpace();
continue;
}

if (stream.eatWhile(reWord)) { if (stream.eatWhile(reWord)) {
var attrName = stream.current(); attrName = stream.current();
var attrValue = ''; attrValue = '';
if (stream.peek() == '=') { nextChar = stream.peek();
if (nextChar == '=') {
stream.next(); stream.next();
stream.start = stream.pos; stream.start = stream.pos;
var quote = stream.peek();

attrValue = eatQuotedString(stream);
if (quote == '"' || quote == "'") { if (attrValue === false) {
stream.next(); throw 'Invalid attribute value';
if (consumeQuotedValue(stream, quote)) { } else if (attrValue === null) {
if (stream.eatWhile(/[^\s\]]/)) {
attrValue = stream.current(); attrValue = stream.current();
// strip quotes
attrValue = attrValue.substring(1, attrValue.length - 1);
} else { } else {
throw 'Invalid attribute value'; throw 'Invalid attribute value';
} }
} else if (stream.eatWhile(/[^\s\]]/)) {
attrValue = stream.current();
} else {
throw 'Invalid attribute value';
} }
} else if (nextChar && nextChar != ' ' && nextChar != '\t') {
// special case: unquoted default attribute value,
// e.g. a[http://google.com]
// parse until end or next space character and consume
// curret state as default value
stream.eatWhile(/\S/);
attrName = DEFAULT_ATTR_NAME;
attrValue = stream.current();
} }


result.push({ result.push({
Expand Down Expand Up @@ -679,8 +727,9 @@ define(function(require, exports, module) {
nameEnd = stream.pos; nameEnd = stream.pos;


stream.start = stream.pos; stream.start = stream.pos;
if (!stream.skipToPair('[', ']')) if (!stream.skipToPair('[', ']')) {
throw 'Invalid attribute set definition'; throw 'Invalid attribute set definition';
}


result = result.concat( result = result.concat(
extractAttributes(stripped(stream.current())) extractAttributes(stripped(stream.current()))
Expand Down Expand Up @@ -832,7 +881,7 @@ define(function(require, exports, module) {
outputProcessors.push(_.bind(tabStops.abbrOutputProcessor, tabStops)); outputProcessors.push(_.bind(tabStops.abbrOutputProcessor, tabStops));


// include default pre- and postprocessors // include default pre- and postprocessors
_.each([lorem, procResourceMatcher, procPastedContent, procTagName], function(mod) { _.each([lorem, procResourceMatcher, procAttributes, procPastedContent, procTagName], function(mod) {
if (mod.preprocessor) { if (mod.preprocessor) {
preprocessors.push(_.bind(mod.preprocessor, mod)); preprocessors.push(_.bind(mod.preprocessor, mod));
} }
Expand All @@ -843,6 +892,8 @@ define(function(require, exports, module) {
}); });


return { return {
DEFAULT_ATTR_NAME: DEFAULT_ATTR_NAME,

/** /**
* Parses abbreviation into tree with respect of groups, * Parses abbreviation into tree with respect of groups,
* text nodes and attributes. Each node of the tree is a single * text nodes and attributes. Each node of the tree is a single
Expand Down
42 changes: 42 additions & 0 deletions lib/parser/processor/attributes.js
@@ -0,0 +1,42 @@
/**
* Resolves node attribute names: moves `default` attribute value
* from stub to real attribute.@async
*
* This resolver should be applied *after* resource matcher
*/
if (typeof module === 'object' && typeof define !== 'function') {
var define = function (factory) {
module.exports = factory(require, exports, module);
};
}

define(function() {
var _ = require('lodash');

function process(node, parser) {
_.each(node.children, function(item) {
var defaultAttrValue = item.attribute(parser.DEFAULT_ATTR_NAME);
if (!_.isUndefined(defaultAttrValue)) {
// remove stub attribute
item.attribute(parser.DEFAULT_ATTR_NAME, null);

// TODO locate *real* default attribute
var attrList = item.attributeList();
if (attrList.length) {
item.attribute(attrList[0].name, defaultAttrValue);
}
}
});
}

return {
/**
* @param {AbbreviationNode} tree
* @param {Object} options
* @param {abbreviation} parser
*/
preprocessor: function(tree, options, parser) {
process(tree, parser);
}
};
});
8 changes: 8 additions & 0 deletions tests/expandAbbreviations.js
Expand Up @@ -95,6 +95,14 @@ describe('Abbreviation Expander engine', function() {
assert.equal(expand('filelist[id=javascript.files]'), '<filelist id="javascript.files"></filelist>'); assert.equal(expand('filelist[id=javascript.files]'), '<filelist id="javascript.files"></filelist>');
}); });


it.only('Default attributes', function() {
assert.equal(expand('a["text.html"]'), '<a href="text.html"></a>');
assert.equal(expand('a[\'text.html\']'), '<a href="text.html"></a>');
assert.equal(expand('a[text.html]'), '<a href="text.html"></a>');
assert.equal(expand('a[http://google.com title=Google]'), '<a href="http://google.com" title="Google"></a>');
assert.equal(expand('a[title=Google http://google.com]'), '<a href="http://google.com" title="Google"></a>');
});

it('Expandos', function() { it('Expandos', function() {
assert.equal(expand('dl+'), '<dl><dt></dt><dd></dd></dl>'); assert.equal(expand('dl+'), '<dl><dt></dt><dd></dd></dl>');
assert.equal(expand('div+div>dl+'), '<div></div><div><dl><dt></dt><dd></dd></dl></div>'); assert.equal(expand('div+div>dl+'), '<div></div><div><dl><dt></dt><dd></dd></dl></div>');
Expand Down

0 comments on commit ee1b56c

Please sign in to comment.