1

I am currently using the following script to convert indented plain text to HTML list code:

jQuery(function($) {
var indentedToHtmlList = function indentedToHtmlList (text, indentChar, folderChar, listType, showIcons) {
  indentChar = indentChar || '\t';
  folderChar = folderChar || ':';
  listType = listType || 'ul';
  showIcons = !!showIcons;

  var lastDepth,
      lines = text.split(/\r?\n/),
      output = '<' + listType + '>\n',
      depthCounter = new RegExp('^(' + indentChar + '*)(.*)');

  for (var i = 0; i < lines.length; i++) {
    var splitted = lines[i].match(depthCounter),
        indentStr = splitted[1],
        fileName = splitted[2],
        currentDepth = (indentStr === undefined) ? 0 : (indentStr.length / indentChar.length),
        isFolder = (fileName.charAt(fileName.length - 1) === folderChar);

    if (isFolder) {
      fileName = fileName.substring(0, fileName.length -1);
    }

    if (lastDepth === currentDepth) {
      output += '</li>\n';
    } else if (lastDepth > currentDepth) {
      while (lastDepth > currentDepth) {
        output += '</li>\n</' + listType + '>\n</li>\n';
        lastDepth--;
      }
    } else if (lastDepth < currentDepth) {
      output += '\n<' + listType + '>\n';
    }

    output += '<li>';
    if (showIcons) {
      output += '<span class=" glyphicon glyphicon-' +
      (isFolder ? 'folder-open' : 'file') +
      '"></span> ';
    }
    output += fileName;

    lastDepth = currentDepth;
  }

  while (lastDepth >= 0) {
    output += '\n</li>\n</' + listType + '>';
    lastDepth--;
  }

  return output;
};

runConvert = function() {
  var originalText = $('#textarea-plain-text').val(),
      listType = $('#list-type').val(),
      showIcons = !!$('#glyph-selector-box').prop('checked'),
      result = indentedToHtmlList(originalText, '\t', ':', listType, showIcons);
  $('#textarea-converted-text').val(result);
  return $('#div-converted-text').html(result);
};

bind = function() {
  return $('#list-conversion-button').click(runConvert);
};

$(bind);
});

The script outputs code which displays properly, which is part of the reason it took a while to notice that the syntax is off. Here is an example text, the converted version (i.e., the script's result), and a marked up version of the converted text showing where the errors are:

  1. Indented plain text sample.
  2. Script output.
  3. List of errors (as detected by BBEdit).
  4. Marked up output (errors commented and corrections added).

From what I can tell, it appears the script is inserting extraneous </li> tags and placing closing </ul> tags on nested lists too high in the hierarchy. Editing the first while loop seems to be the solution to the </li> issue, but I, and another developer, cannot figure out where the logic is going awry on the </ul>'s.

Here is a page which currently has the script implemented (so you can generate your own examples without creating a page to do so): Convert Indented/Nested Plain Text to an HTML List.

7
  • 1
    Well this doesn't solve the mystery but you really don't need the </li> closing tags at all. You do need the closing </ul> tags however. Commented Dec 17, 2013 at 1:32
  • @Pointy, thanks for the information. Looks like W3C says you can omit </li> so long as it is immediately followed by another <li> or there is no more content in the parent element: w3.org/TR/html-markup/li.html. Good to know. Commented Dec 17, 2013 at 1:47
  • Why don't you take advantage of jQuery and use it to create and append the elements? Commented Dec 17, 2013 at 2:43
  • @HugoSilva, that would be one possible way to rework the script, but I imagine I'd run into the same logic issue I'm facing now (i.e., the nesting problem with </ul>'s. I'm not actually attached to any particular way of accomplishing the end goal (i.e., converting indented plain text to a nested HTML list). Commented Dec 17, 2013 at 3:09
  • @Zyniker, I haven't actually gone through the whole code because it is hard to read. There are no comments, and string manipulation everywhere. You may be right about the nesting problem, but I am sure a cleaner code will help you on spotting the flaw ;) Commented Dec 17, 2013 at 3:25

1 Answer 1

1

What this code is doing is building a linear string, which is very confusing and error prone. Not mentioning the lack of comments on the code. By taking advantage of jQuery you can create elements as objects and then manipulate them, without even having to worry about markup. I haven't tested the code below, it is more as an example of what I just said:

function indentedToHtmlList (text, indentChar, folderChar, listType, showIcons) {
    indentChar = indentChar || '\t';
    folderChar = folderChar || ':';
    listType = listType || 'ul';
    showIcons = !!showIcons;

    var lines = text.split(/\r?\n/),
        currentLevel = 1,
        rootLevel,
        currentList = rootLevel = $('<' + listType + '/>'),
        previousItem,
        previousLists = [rootLevel];

    for(var i = 0; i < lines.length; i++){

        //split line into array
        var line = lines[i].split(indentChar);

        //handle levels
        if(line.length > currentLevel){
            //add current list to history and create new list
            previousLists.push(currentList);
            currentList = $('<' + listType + '/>');
            previousItem.append(currentList);
            currentLevel++;
        } else if(line.length < currentLevel){
            //get last list from history, until matches current level
            while(line.length < currentLevel){
                currentList = previousLists.pop();
                currentLevel--;
            }
        }

        //create current item
        var itemText = line[line.length - 1];
        var item = $('<li/>').text(itemText);

        //check if is folder
        var isFolder = itemText.charAt(itemText.length - 1) === folderChar;

        //handle icon
        if (showIcons) {
            item.prepend($('<span/>').addClass('glyphicon glyphicon-' + isFolder ? 'folder-open' : 'file'));
        }

        // add item to list
        console.log(currentList);
        currentList.append(item);

        previousItem = item;
    }

    return rootLevel.html();

}

Sometimes is just quicker to rewrite bad code, than finding a single character you need to change. In your case I would definitely rewrite it...

Sign up to request clarification or add additional context in comments.

4 Comments

That script actually appears to work...almost perfectly. With two small exceptions, it generates syntactically valid code: it doesn't add the top-level <ul> (and it's closing companion) and it seem to apply the folder-open class to every span without the glyphicon class or glyphicon- prefix on folder-open. The first bit should be a relatively easy fix, but I'm not sure what's going on with the <span> classes.
I believe I got it working as intended: pastebin.com/8f7Tn5YA. Just need to figure out how best to add a space after <span>'s with the glyphicon-folder-open class.
Decided to go with a CSS solution instead of hard-coding in the spaces after the folders (it's just a compensation for the fact that the folder glyphicons are wider than some others and, thus, encroach upon their surrounding text). span.glyphicon-folder-open { padding-right: 1.5em; }
Good on you mate. Even though it wasn't working, I see you were able to fix it in about an hour... That was my point all along :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.