Permalink
Browse files

Working on default attribute values

  • Loading branch information...
1 parent ada5b85 commit ee1b56c31e5e71e6db3ba9e2255fe7d93140cc7f @sergeche sergeche committed Oct 29, 2013
Showing with 158 additions and 40 deletions.
  1. +0 −1 .gitignore
  2. +18 −0 emmet.sublime-project
  3. +90 −39 lib/parser/abbreviation.js
  4. +42 −0 lib/parser/processor/attributes.js
  5. +8 −0 tests/expandAbbreviations.js
View
@@ -10,5 +10,4 @@
.DS_Store
/bower_components/
/node_modules/
-/emmet.sublime-project
/emmet.sublime-workspace
View
@@ -0,0 +1,18 @@
+{
+ "folders":
+ [
+ {
+ "follow_symlinks": true,
+ "path": "lib"
+ }
+ ],
+
+ "ternjs": {
+ "include": ["/lib/**/*.js"],
+ "disable_completions": ["**/parser/abbreviation.js"],
+ "plugins": {
+ "node": {},
+ "doc_comment": {}
+ }
+ }
+}
@@ -31,9 +31,11 @@ define(function(require, exports, module) {
var procPastedContent = require('./processor/pastedContent');
var procTagName = require('./processor/tagName');
var procResourceMatcher = require('./processor/resourceMatcher');
+ var procAttributes = require('./processor/attributes');
var reValidName = /^[\w\-\$\:@\!%]+\+?$/i;
var reWord = /[\w\-:\$@]/;
+ var DEFAULT_ATTR_NAME = '%default';
var pairs = {
'[': ']',
@@ -173,22 +175,7 @@ define(function(require, exports, module) {
* @returns {AbbreviationNode}
*/
find: function(fn) {
- return this.findAll(fn)[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;
+ return this.findAll(fn, {amount: 1})[0];
},
/**
@@ -197,16 +184,23 @@ define(function(require, exports, module) {
* @param {Function} fn
* @returns {Array}
*/
- findAll: function(fn) {
+ findAll: function(fn, state) {
+ state = _.extend({amount: 0, found: 0}, state || {});
+
if (!_.isFunction(fn)) {
var elemName = fn.toLowerCase();
fn = function(item) {return item.name().toLowerCase() == elemName;};
}
var result = [];
_.each(this.children, function(child) {
- if (fn(child))
+ if (fn(child)) {
result.push(child);
+ state.found++;
+ if (state.amount && state.found >= state.amount) {
+ return;
+ }
+ }
result = result.concat(child.findAll(fn));
});
@@ -270,11 +264,26 @@ define(function(require, exports, module) {
/**
* Returns or sets attribute value
* @param {String} name Attribute name
- * @param {String} value New attribute value
+ * @param {String} value New attribute value. `Null` value
+ * will remove attribute
* @returns {String}
*/
attribute: function(name, value) {
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
var ix = _.indexOf(_.pluck(this._attributes, 'name'), name.toLowerCase());
if (~ix) {
@@ -580,45 +589,84 @@ define(function(require, exports, module) {
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:
- * <code>[attr col=3 title="Quoted string"]</code>
+ * <code>[attr col=3 title="Quoted string"]</code> (without square braces)
* @param {String} attrSet
* @returns {Array}
*/
- function extractAttributes(attrSet, attrs) {
+ function extractAttributes(attrSet) {
attrSet = utils.trim(attrSet);
var result = [];
-
- /** @type StringStream */
+ var attrName, attrValue, quote, nextChar;
+
var stream = stringStream.create(attrSet);
stream.eatSpace();
while (!stream.eol()) {
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)) {
- var attrName = stream.current();
- var attrValue = '';
- if (stream.peek() == '=') {
+ attrName = stream.current();
+ attrValue = '';
+ nextChar = stream.peek();
+ if (nextChar == '=') {
stream.next();
stream.start = stream.pos;
- var quote = stream.peek();
-
- if (quote == '"' || quote == "'") {
- stream.next();
- if (consumeQuotedValue(stream, quote)) {
+
+ attrValue = eatQuotedString(stream);
+ if (attrValue === false) {
+ throw 'Invalid attribute value';
+ } else if (attrValue === null) {
+ if (stream.eatWhile(/[^\s\]]/)) {
attrValue = stream.current();
- // strip quotes
- attrValue = attrValue.substring(1, attrValue.length - 1);
} else {
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({
@@ -679,8 +727,9 @@ define(function(require, exports, module) {
nameEnd = stream.pos;
stream.start = stream.pos;
- if (!stream.skipToPair('[', ']'))
+ if (!stream.skipToPair('[', ']')) {
throw 'Invalid attribute set definition';
+ }
result = result.concat(
extractAttributes(stripped(stream.current()))
@@ -832,7 +881,7 @@ define(function(require, exports, module) {
outputProcessors.push(_.bind(tabStops.abbrOutputProcessor, tabStops));
// include default pre- and postprocessors
- _.each([lorem, procResourceMatcher, procPastedContent, procTagName], function(mod) {
+ _.each([lorem, procResourceMatcher, procAttributes, procPastedContent, procTagName], function(mod) {
if (mod.preprocessor) {
preprocessors.push(_.bind(mod.preprocessor, mod));
}
@@ -843,6 +892,8 @@ define(function(require, exports, module) {
});
return {
+ DEFAULT_ATTR_NAME: DEFAULT_ATTR_NAME,
+
/**
* Parses abbreviation into tree with respect of groups,
* text nodes and attributes. Each node of the tree is a single
@@ -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);
+ }
+ };
+});
@@ -95,6 +95,14 @@ describe('Abbreviation Expander engine', function() {
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() {
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>');

0 comments on commit ee1b56c

Please sign in to comment.