Skip to content

Commit

Permalink
Add stub .properties file parser/reader, QUnit tests for it.
Browse files Browse the repository at this point in the history
  • Loading branch information
bvibber committed Nov 10, 2011
1 parent daf7637 commit 626a82b
Show file tree
Hide file tree
Showing 5 changed files with 2,174 additions and 0 deletions.
72 changes: 72 additions & 0 deletions assets/www/js/propertiesFileReader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* Copyright (c) Brion Vibber 2011
* Written initially for Wikipedia Mobile app
*
* Reader for Java-style .properties files to be used for
* l10n message files.
*
* http://download.oracle.com/javase/7/docs/api/java/util/Properties.html#load%28java.io.Reader%29
*/

propertiesFileReader = window.propertiesFileReader = {
/**
* @param string data: full string of source file
* @return Object: map of keys to strings
* @throws Error on invalid input format
*/
parse: function(text) {
if (typeof text !== "string") {
throw new Error("Non-string passed to propertiesFileReader.parse");
}
var data = {},
lines = text.split(/\r?\n/),
blank = /^\s*$/,
comment = /^\s*[#!]/,
keyonly = /^\s*([^=:\s]+)\s*()$/,
keyval = /^\s*([^=:\s]+)(?:\s*[=:]\s*|\s+)(.*)$/,
continued = /^\s*(.*)$/;
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
if (line.match(blank)) {
continue;
}
if (line.match(comment)) {
continue;
}
var matches = line.match(keyonly) || line.match(keyval);
if (matches) {
var key = matches[1],
val = matches[2];

while (val.substr(-1, 1) == "\\") {
// Line continuation
i++;
if (i >= lines.length) {
throw new Error("Line continuation at end of file at line " + i);
}
line = lines[i];
if (matches = line.match(continued)) {
val = val.substr(0, val.length - 1) + matches[1];
} else {
throw new Error("Invalid line after line continuation at line " + i);
}
}

data[key] = propertiesFileReader.unescape(val);

continue;
}
throw new Error("Invalid .properties format at line " + (i + 1) + ": " + line);
}
return data;
},

unescape: function(str) {
// @fixme add \u escapes -- won't be used in our files though
str = str.replace(/\\n/g, "\n");
str = str.replace(/\\t/g, "\t");
str = str.replace(/\\(.)/g, "$1");
return str;
}
};

26 changes: 26 additions & 0 deletions tests/qunit/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>QUnit Test Suite</title>

<!-- QUnit libs -->
<link rel="stylesheet" href="qunit.css" type="text/css" media="screen">
<script type="text/javascript" src="qunit.js"></script>

<!-- Modules to test... -->
<script type="text/javascript" src="../../assets/www/js/propertiesFileReader.js"></script>

<!-- Test cases -->
<script type="text/javascript" src="propertiesFileReader-test.js"></script>

</head>
<body>
<h1 id="qunit-header">QUnit Test Suite</h1>
<h2 id="qunit-banner"></h2>
<div id="qunit-testrunner-toolbar"></div>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
<div id="qunit-fixture">test markup</div>
</body>
</html>
253 changes: 253 additions & 0 deletions tests/qunit/propertiesFileReader-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
test('Is module obj defined?', function() {
expect(1);
ok(typeof propertiesFileReader === "object");
});

function parseEqual(message, input, expected) {
deepEqual(propertiesFileReader.parse(input), expected, message);
}

test('Can parse very simple input?', function() {
parseEqual(
"Single line, no newline",
"key=value",
{
key: "value"
}
);
parseEqual(
"Three simple lines, no final newline",
"key=value\nkey2=value2\nkey3=value3",
{
key: "value",
key2: "value2",
key3: "value3"
}
);
});

test('Can ignore blank/empty lines?', function() {
parseEqual(
"Single line with newline",
"key=value\n",
{
key: "value"
}
);
parseEqual(
"Three simple lines, some extra newlines",
"\n\nkey=value\nkey2=value2\n\nkey3=value3\n",
{
key: "value",
key2: "value2",
key3: "value3"
}
);
parseEqual(
"Real lines around a blank empty line",
"key=value\n\nkey2=value2",
{
key: "value",
key2: "value2",
}
);
parseEqual(
"Real lines around a line of spaces",
"key=value\n \nkey2=value2",
{
key: "value",
key2: "value2",
}
);
parseEqual(
"Real lines around a line of tabs",
"key=value\n\t\t\t\nkey2=value2",
{
key: "value",
key2: "value2",
}
);
parseEqual(
"Real lines around a line of form feeds",
"key=value\n\f\f\f\nkey2=value2",
{
key: "value",
key2: "value2",
}
);
});

test('Can ignore comment lines?', function() {
parseEqual(
"Three simple lines, one commented out with #",
"#key=value\nkey2=value2\nkey3=value3",
{
key2: "value2",
key3: "value3"
}
);
parseEqual(
"Three simple lines, one commented out with !",
"key=value\n!key2=value2\nkey3=value3",
{
key: "value",
key3: "value3"
}
);
parseEqual(
"Three simple lines, one commented out with # after spaces",
" #key=value\nkey2=value2\nkey3=value3",
{
key2: "value2",
key3: "value3"
}
);
parseEqual(
"Three simple lines, one commented out with ! after spaces",
"key=value\n !key2=value2\nkey3=value3",
{
key: "value",
key3: "value3"
}
);
});

test('Key format', function() {
var keyEqual = function(message, input) {
// All these tests end the same. :)
parseEqual(message, input, { key: "value" });
};
keyEqual(
"Simple key",
"key=value"
);
keyEqual(
"Simple key with preceding whitespace",
" key=value"
);
keyEqual(
"Simple key with preceding whitespace (tab)",
"\tkey=value"
);
keyEqual(
"Simple key with following whitespace",
"key =value"
);
keyEqual(
"Simple key with preceding and following whitespace",
" key =value"
);
keyEqual(
"Key with : as sep",
"key:value"
);
keyEqual(
"Key with space as sep",
"key value"
);
});

test('Whitespace examples from doc', function() {
// http://download.oracle.com/javase/7/docs/api/java/util/Properties.html#load%28java.io.Reader%29
var keyEqual = function(input) {
// All these tests end the same. :)
parseEqual(input, input, { Truth: "Beauty" });
};
keyEqual("Truth = Beauty");
keyEqual(" Truth:Beauty");
keyEqual("Truth :Beauty");
});

test('Line extension and whitespace example from doc', function() {
// http://download.oracle.com/javase/7/docs/api/java/util/Properties.html#load%28java.io.Reader%29
parseEqual(
"Line extension & ignore initial whitespace on following lines",
"fruits apple, banana, pear, \\\n" +
" cantaloupe, watermelon, \\\n" +
" kiwi, mango",
{
fruits: "apple, banana, pear, cantaloupe, watermelon, kiwi, mango"
}
);
});

test('Empty value with no separator; doc example', function() {
parseEqual(
"Line with no value & no separator",
"cheeses",
{
cheeses: ""
}
);
});

test('Escapes', function() {
parseEqual(
"\\n",
"key=Foo\\nbar",
{
key: "Foo\nbar"
}
);
parseEqual(
"\\t",
"key=Foo\\tbar",
{
key: "Foo\tbar"
}
);
parseEqual(
"\\\\",
"key=Foo\\\\bar",
{
key: "Foo\\bar"
}
);
parseEqual(
"\\b (b, not backspace)",
"key=Foo\\bbar",
{
key: "Foobbar"
}
);
parseEqual(
"\\z (z, not ctrl+z)",
"key=Foo\\zbar",
{
key: "Foozbar"
}
);
parseEqual(
"\\n (multiple)",
"key=Foo\\nbar\\nbaz\\nbing",
{
key: "Foo\nbar\nbaz\nbing"
}
);
});

test('Small sample file', function() {
parseEqual('Small sample file',
"# English-language messages for Wikipedia Mobile app\n" +
"sitename = Wikipedia\n" +
"spinner-loading = Loading\n" +
"spinner-retrieving = Retrieving content from $1\n" +
"bookmarks-max-warning = You've reached the maximum number of bookmarks.\n" +
"bookmark-added = $1 added to bookmarks.\n" +
"bookmark-exists $1 already exists in bookmarks.\n" +
"bookmark-remove-prompt = Remove $1 from bookmarks?\n" +
"bookmark-removed = $1 has been removed.\n",
{
'sitename': 'Wikipedia',
'spinner-loading': 'Loading',
'spinner-retrieving': 'Retrieving content from $1',
'bookmarks-max-warning': "You've reached the maximum number of bookmarks.",
'bookmark-added': "$1 added to bookmarks.",
'bookmark-exists': "$1 already exists in bookmarks.",
'bookmark-remove-prompt': "Remove $1 from bookmarks?",
'bookmark-removed': "$1 has been removed."
}
);
});


Loading

0 comments on commit 626a82b

Please sign in to comment.