docs: function postProcessing(tokens, options, source){
  
    function tokenName(token){
      return (token.prefix ? token.prefix + ':' : '') + token.name;
    }
  
    function getTokenAttrs(token){
      return token.attrs.reduce(function(res, attr){
        res[tokenName(attr)] = attr.value;
        return res;
      }, {});
    }
  
    function buildLocationIndex(){
      var line = 1;
      var column = 0;
  
      lineIdx = new Array(source.length);
      columnIdx = new Array(source.length);
  
      for (var i = 0; i < source.length + 1; i++)
      {
        lineIdx[i] = line;
        columnIdx[i] = column;
  
        if (source[i] === '\n')
        {
          line++;
          column = 0;
        }
        else
          column++;
      }
    }
  
  
    function findLocationByOffset(offset){
      return {
        line: lineIdx[offset],
        column: columnIdx[offset]
      };
    }
  
    function getLocationFromRange(range){
      return {
        start: findLocationByOffset(range.start_),
        end: findLocationByOffset(range.end_)
      };
    }
  
    function convertRange(token){
      if (options.loc)
      {
        token.loc = getLocationFromRange(token.range);
        if (token.valueRange)
          token.valueLoc = getLocationFromRange(token.valueRange);
      }
  
      if (options.range)
      {
        token.range = [token.range.start_, token.range.end_];
        if (token.valueRange)
          token.valueRange = [token.valueRange.start_, token.valueRange.end_];
      }
      else
      {
        delete token.range;
        delete token.valueRange;
      }
    }
  
    function walk(tokens){
      var token;
      var prev;
  
      for (var i = 0; token = tokens[i++]; prev = token)
      {
        // process element
        if (token.type == TYPE_ELEMENT)
        {
          // process attribute content
          var attrs = getTokenAttrs(token);
          for (var j = 0, attr; attr = token.attrs[j++];)
          {
            var mode = attr.name;
  
            // special case modification instruction in <b:include>
            if (token.prefix == 'b' && attr.name == 'value')
            {
              // parse value attribute in
              //   <b:class>/<b:append-class>/<b:set-class>/<b:remove-class>
              //   <b:attr name="name">/<b:append-attr name="name">/<b:set-attr name="name">/<b:remove-attr name="name">
              var m = token.name.match(ATTRIBUTE_MODE);
              if (m)
                mode = m[1] == 'class' ? 'class' : attrs.name;
            }
  
            // process bindings and decode HTML tokens
            processAttr(attr, mode, convertRange);
  
            convertRange(attr);
          }
  
          // walk recursive
          walk(token.children);
        }
  
        // process text
        if (token.type == TYPE_TEXT)
        {
          // decode HTML tokens in value
          token.value = decodeHTMLTokens(token.value);
  
          // try join text tokens
          if (!token.refs && prev && prev.type == TYPE_TEXT && !prev.refs)
          {
            prev.value += token.value;
            prev.end_ = token.end_;
            tokens.splice(--i, 1);
          }
        }
  
        // process comment
        if (token.type == TYPE_COMMENT)
        {
          // decode HTML tokens in value
          token.value = decodeHTMLTokens(token.value);
        }
  
        convertRange(token);
      }
    }
  
    var lineIdx;
    var columnIdx;
  
    if (options.loc)
      buildLocationIndex();
  
    walk(tokens);
  }