SON of JSON

by Tonio Loewald

HTML

<h1>SON of JSON</h1>
<pre>
    
simple-stuff: hello, world
array:
  :first
  :second
  :
    name: third
    favorite-color: pink
  :fourth
    
mixed:
  :test
  foo: bar
  :another test
  baz: bro
nest:
  bird: egg
  suit: radiation-proof
elt-list:
  :
    type: div
    content:
      :
        type: p
        attributes:
          class: foo
        content: Some text
      :
        type: p
        attributes:
          style: color: red;
        content: More text
</pre>

CSS

.failure {
    color: red;
}

.success {
    color: #090;
}

[data-result]:after {
    content: attr(data-result);
    margin-left: 8px;
    padding: 0 4px;
    background-color: #ffa;
}

[data-exception]:after {
    content: attr(data-exception);
    margin-left: 8px;
    padding: 0 4px;
    background-color: #faa;
}

JavaScript

console.clear();

function Sack(source, spaces){
    if(spaces === undefined){
        spaces = 2;
    }
    var tabEquivalent = new RegExp(' {' + spaces + '}', 'g');
    this.parsed = false;
    this.source = source.replace(/ {2}/g, '\t');
    if(this.source.match(/^\t* /)){
        console.error('odd number of leading spaces in input', this.source.match(/^\t* /g));
    }
    this.values = {};
    
    return this;
}

Sack.prototype = {
    parse: function(){
        if(!this.parsed){
            var lines = this.source.split(/\n/);
			var idx = 0;
            
            while(lines.length){
                var line = lines.shift();
                if(line.trim() && line.substr(0,1) !== '\t'){
                    var cut = line.indexOf(':');
                    if(cut === -1){
                        throw "bad input, expected colon: " + line;
                    }
                    var key = line.substr(0, cut) || idx;
                    idx += 1;
                    var value = line.substr(cut + 1);
                    // skip first space if present
                    if(value.length && value.substr(0,1) === ' '){
                        value = value.substr(1);
                    }
                    if(value !== ''){
                        if(value.trim() === ''){
                            console.warn('Possible trailing space: ' + line);
                        }
                        this.values[key] = value;
                    } else {
                        value = [];
                        var line = '';
                        while(lines.length && lines[0].substr(0,1) === '\t'){
                            line = lines.shift();
                            value.push(line.substr(1));
                        }
                        value = value.join('\n');
                        this.values[key] = new Sack(value);
                    }
                }
            }
            this.parsed = true;
        }
        return this;
   ...