diff --git a/source/documents/_scripts.html.jade b/source/documents/_scripts.html.jade
index dae01cb..9d25b4a 100644
--- a/source/documents/_scripts.html.jade
+++ b/source/documents/_scripts.html.jade
@@ -1,4 +1,5 @@
// application scripts
+script(src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/1.2.1/lodash.js")
script(src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.1.5/angular.js")
// TODO use cdnjs.com once they serve marked
script(src="//raw.github.com/chjj/marked/master/lib/marked.js")
@@ -6,4 +7,5 @@ script(src="scripts/app.js")
script(src="scripts/services/reader/_factory.js")
script(src="scripts/services/reader/markdown.js")
script(src="scripts/services/reader/json.js")
+script(src="scripts/services/transformer.js")
script(src="scripts/controllers.js")
diff --git a/source/documents/scripts/services/transformer.js.coffee b/source/documents/scripts/services/transformer.js.coffee
new file mode 100644
index 0000000..d7aab61
--- /dev/null
+++ b/source/documents/scripts/services/transformer.js.coffee
@@ -0,0 +1,81 @@
+angular.module("sideBySide").factory("transformer", () ->
+ # Transform translations
+ #
+ # @param translations [...Object] Translation with 'meta' and 'content' keys
+ # @throw On uneven number of verses
+ # @return [Array] Translations transformed into row-based format
+ return (translations...) ->
+ # Check whether all translations have same number of verses
+ #
+ # @param translations [array] Translations to check
+ check_lengths = (translations) ->
+ lengths = []
+ for translation in translations
+ l = translation.content.length
+ lengths[l] = 0 if l not of lengths
+ lengths[l]++
+
+ # are all same length?
+ if _.size(_.compact(lengths)) <= 1
+ return
+
+ uneven = []
+ for i,j in lengths
+ if typeof i != "undefined"
+ uneven.push(i + " of length " + j)
+
+ details = []
+ while _.size(_.compact(lengths)) > 1
+ mindex = _.indexOf(lengths, _.min(lengths))
+ maxdex = _.indexOf(lengths, _.max(lengths))
+
+ # least occuring count is higher than most occuring count
+ if mindex > maxdex
+ message = "Superfluous verses: "
+ else
+ message = "Missing verses after: "
+
+ # get last verses of translations with mindex verses
+ lastVerses = _.map(translations.filter((translation) ->
+ return translation.content.length == mindex
+ ), (translation) ->
+ return "'" + _.last(translation.content).text + "'"
+ )
+
+ details.push(message + lastVerses.join(", "))
+
+ # remove least occuring from lengths
+ delete lengths[mindex]
+
+ throw "Poems with uneven number of verses: " +
+ uneven.join(", ") + "." +
+ " (" + details.join(". ") + ")."
+
+
+ check_lengths(translations)
+
+ data = {
+ meta: []
+ verses: []
+ }
+
+ for translation in translations
+ data.meta.push(translation.meta)
+
+ id = 0
+ while true
+ versions = []
+ found = false
+ for translation in translations
+ if translation.content.length > id
+ found = translation.content[id]
+ versions.push(found)
+
+ if found == false
+ break
+
+ data.verses.push(versions)
+ id++
+
+ return data
+)
diff --git a/source/documents/tests.html.jade b/source/documents/tests.html.jade
index d97fd9f..77bc0f5 100644
--- a/source/documents/tests.html.jade
+++ b/source/documents/tests.html.jade
@@ -14,6 +14,7 @@ html
script(src="tests/services/reader/_abstract.js")
script(src="tests/services/reader/markdown.js")
script(src="tests/services/reader/json.js")
+ script(src="tests/services/transformer.js")
body
div(id="qunit")
diff --git a/source/documents/tests/services/transformer.js.coffee b/source/documents/tests/services/transformer.js.coffee
new file mode 100644
index 0000000..560da4f
--- /dev/null
+++ b/source/documents/tests/services/transformer.js.coffee
@@ -0,0 +1,114 @@
+test("transformer", () ->
+ english = {
+ meta: {
+ Title: "Ten Commandments"
+ Author: "God knows"
+ }
+ content: [{
+ section: "1"
+ text: "Thou shalt have no other gods before me"
+ }, {
+ section: "2"
+ text: "Thou shalt not take the name of the Lord thy God in vain"
+ }, {
+ section: "3"
+ text: "Remember the sabbath day, to keep it holy"
+ }]
+ }
+
+ czech = {
+ meta: {
+ Title: "Desatero"
+ Author: "Ví bůh"
+ }
+ content: [{
+ section: "1"
+ text: "V jednoho Boha věřiti budeš."
+ }, {
+ section: "2"
+ text: "Nevezmeš jména Božího nadarmo."
+ }, {
+ section: "3"
+ text: "Pomni, abys den sváteční světil."
+ }]
+ }
+
+ polish = {
+ meta: {
+ Title: "Dekalog"
+ Author: "Bóg wie"
+ }
+ content: [{
+ section: "1"
+ text: "Nie będziesz miał bogów cudzych przede mną."
+ }, {
+ section: "2"
+ text: "Nie będziesz wzywał imienia Boga twego nadaremno."
+ }, {
+ section: "3"
+ text: "Pamiętaj, abyś dzień święty święcił."
+ }]
+ }
+
+ expected = {
+ meta: [{
+ Title: "Ten Commandments"
+ Author: "God knows"
+ }, {
+ Title: "Desatero"
+ Author: "Ví bůh"
+ }, {
+ Title: "Dekalog"
+ Author: "Bóg wie"
+ }]
+
+ verses: [[{
+ section: "1"
+ text: "Thou shalt have no other gods before me"
+ }, {
+ section: "1"
+ text: "V jednoho Boha věřiti budeš."
+ }, {
+ section: "1"
+ text: "Nie będziesz miał bogów cudzych przede mną."
+ }], [{
+ section: "2"
+ text: "Thou shalt not take the name of the Lord thy God in vain"
+ }, {
+ section: "2"
+ text: "Nevezmeš jména Božího nadarmo."
+ }, {
+ section: "2"
+ text: "Nie będziesz wzywał imienia Boga twego nadaremno."
+ }], [{
+ section: "3"
+ text: "Remember the sabbath day, to keep it holy"
+ }, {
+ section: "3"
+ text: "Pomni, abys den sváteční světil."
+ }, {
+ section: "3"
+ text: "Pamiętaj, abyś dzień święty święcił."
+ }]]
+ }
+
+ # Test transformation works as it should
+ transformer = injector.get("transformer")
+ deepEqual(transformer(english, czech, polish), expected)
+
+ # Test various error messages as we remove items
+ czech.content.pop()
+ throws(() ->
+ transformer(english, czech, polish)
+ , /missing.*nevezmeš jména/i)
+
+ polish.content.pop()
+ throws(() ->
+ transformer(english, czech, polish)
+ , /superfluous.*sabbath/i)
+
+ polish.content.pop()
+ throws(() ->
+ transformer(english, czech, polish)
+ , /missing.*bogów cudzych.*nevezmeš/i)
+)