Blog-Archiv

Freitag, 31. Juli 2020

Stack Trace Reducer

I find this utility extremely useful, so I expose it here 'for our pleasure'. It not only reduces stack trace lines but any kind of lines, you just need to adjust the filters. Leading spaces are ignored.

stack trace lines to what is significant and interesting.

Input
Output
Filters
Significant Lines
OR
OR
OR
OR
OR
BUT NOT
AND NOT

A significant line will be preceded by "...." and its preceding skipped line. At end of all significant lines there will be the following skipped line and a "....". In case a significant line is followed by an interesting one, there will be no "...." in between.

Interesting Lines
OR
OR
OR
OR
OR
BUT NOT
AND NOT

An interesting line is like a significant one, except that the preceding and the following skipped lines will not be around it. In case an interesting line is followed by a significant one, there will be no "...." in between.

Terminating Lines
OR
OR
OR
OR
OR

After a terminating line, nothing will be output any more, in case at least one significant line was found before. A terminating line would be displayed just when it directly follows a significant line.


There are developer stories around this utility. First is the filter-automaton, implemented as state pattern in Java and then transpiled into JavaScript via JSweet. Second is the layout of the textareas, you may have noticed that they are resizable and responsive and similar to a split-pane. I am looking forward to tell these stories in further Blogs.




Montag, 20. Juli 2020

Format Java Stack Trace without Newlines

There seem to be situations where a stack trace loses its newlines. That's annoying, because I recently searched for an online tool that facilitates restoring them, but didn't find one. Some actually added newlines, but not at "Caused by: ", which you find with nested exceptions. Now here is my ....

Formatter

.... which formats stack traces that lost their newlines.

Stack Trace Input









Formatted Output

When you paste (Ctrl-V) your stack trace line in the left text area and then press "Format", you will hopefully see a beautiful stack trace on the right side.

Mind that the stack trace line still needs to have tabs ('\t', 9). When not, try to activate the "Tab is Space" checkbox.

For the interested, here is the JavaScript that does this (click to expand).
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/** 
 * @param NEWLINE = "WINDOWS" or "MacOS" or "UNIX".
 * @param tabIsSpace true when tabs have been lost in stack trace line.
 */
var stackTraceFormatter = function(NEWLINE, tabIsSpace) {
    NEWLINE = (NEWLINE === "WINDOWS") ? "\r\n" : (NEWLINE === "MacOS") ? "\r" : "\n";
    var TAB = "\t";
    var SEPARATOR = tabIsSpace ? " at " : TAB;
    
    var NEWLINE_WITH_TAB = tabIsSpace ? NEWLINE+TAB+"at " : NEWLINE+TAB;
    var CAUSED_BY = "Caused by: ";
    var NEWLINE_WITH_CAUSED_BY = NEWLINE+CAUSED_BY;
    
    var startsWith = function(text, searchString) {
        return text.substr(0, searchString.length) === searchString;
    };
    
    var that = {};
    
    that.format = function(stackTrace) {
        if (startsWith(stackTrace, SEPARATOR) === false)
          stackTrace = stackTrace.trim();
        
        if (stackTrace.indexOf('\n') >= 0 || stackTrace.indexOf('\r') >= 0)
          return stackTrace;
        
        var s1 = stackTrace.split(SEPARATOR).join(NEWLINE_WITH_TAB);
        var s2 = s1.split(CAUSED_BY).join(NEWLINE_WITH_CAUSED_BY);
        
        return tabIsSpace ? s2.split(" ... ").join(NEWLINE+TAB+"... ") : s2;
    };
    
    return that;
};

  var text = ....;
  var formatter = stackTraceFormatter("UNIX", false);
  var stackTrace = formatter.format(text);



Samstag, 18. Juli 2020

HTML-5 Tree Code Generator

As you may have noticed, HTML-5 features an expand-control element called <details>. The name suggests that there is something more which is not visible. In a passed Blog I showed how you can style such elements to build an expansible/collapsible tree. In this Blog I provide a utility to generate HTML-5 source code out of shorthand text trees.

Mind that Microsoft's Edge browser still doesn't support <details>.

Create an HTML-5 Tree

In the "Input" panel below you see a dashed list that represents a tree structure. The dashes ('-') need to be at line start, but can be indented by spaces. One dash means root level, two dashes second level, and so on. Dashes can be replaced by plus-signs ('+'), representing an initially closed folder. Generally you can configure your own tree level characters. Try it out!

Input
Tree Level Characters:
HTML
Output

Triggering "Generate" will fill the "HTML" text-area with HTML-5 and CSS code, representing the tree according to the dashed list, and it also will render the tree in the "Output" panel.

You can modify the generated HTML/CSS code and view your changes by clicking "Display Changes". Mind that clicking "Generate" will always overwrite what is in "HTML" area.

CSS Technique


<style>
  details.tree {
    padding-left: 1em;
  }
  div.tree {
    padding-left: 2.06em;
  }
</style>

This CSS defines that any <details> element of class tree should be displayed one 'm'-width indented to the right below its parent element. The CSS em is a font-dependent width, which anticipates that all class tree elements need to have the same font size. That makes up the tree structure, recursively as trees are.

The tree leafs are modelled as DIV, because these don't have any browser-specific margin or padding. They go to the right twice as they don't have the expand-control arrow on their left side.

<details class="tree"><summary>Cats</summary>
  <div class="tree">Garfield</div>
  <div class="tree">Catbert</div>
</details>

All you need to do is to classify your <details> elements as tree and nest them into each other. Leafs can be DIV or any other block element, for example a source code listing. Just make sure that leafs not classified as tree are enclosed in a DIV with class tree.

Cats
Garfield
Catbert

Generator JavaScript

For the interested, here is the JavaScript code of the generator. You don't need this for a <details> tree!

JavaScript to convert a dashed text tree to HTML-5 code (click to expand).
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
  /* Module function, reading input */
  var buildTreeElements = function(inputTreeText, expandedLevelCharacter, collapsedLevelCharacter)
  {
    var getIndent = function(trimmedLine) {
      var expanded = true;
      var regExp = new RegExp("^\\"+expandedLevelCharacter+"*");
      var level = regExp.exec(trimmedLine)[0].length;
      if (level <= 0) {
        regExp = new RegExp("^\\"+collapsedLevelCharacter+"*");
        level = regExp.exec(trimmedLine)[0].length;
        expanded = false;
      }
      return {
        level: level,
        expanded: expanded
      };
    };
    
    var getTreeElement = function(lines, startLineIndex, precedingTreeElement) {
      var line = lines[startLineIndex].trim();
      var indent = getIndent(line);
      
      if (indent.level <= 0) { /* text nested into previous tree element */
        if (precedingTreeElement === undefined)
          throw "First tree element must start with '"+expandedLevelCharacter+"' or '"+collapsedLevelCharacter+"' character: '"+line+"'";
      
        return getElementContent(lines, startLineIndex, precedingTreeElement.level + 1);
      }
      
      if (precedingTreeElement && indent.level > precedingTreeElement.level + 1) /* would have no parent */
        throw "Tree level too deep: '"+line+"'";
      
      line = line.slice(indent.level, line.length).trim();
      
      return {
        level: indent.level,
        expanded: indent.expanded,
        content: line,
        endLineIndex: startLineIndex
      };
    };
      
    var getElementContent = function(lines, startLineIndex, level) {
      var LINE_SEPARATOR = "<br/>"; /* catenation character for multiple lines */
      var multiLines = lines[startLineIndex].trim();
      var i = startLineIndex + 1;
      var endLineIndex = i;
      
      for (var done = false; ! done && i < lines.length; i++) {
        var nextLine = lines[i].trim();
        var indent = getIndent(nextLine);
        if (indent.level <= 0) {
          multiLines = multiLines + LINE_SEPARATOR + nextLine;
          endLineIndex = i;
        }
        else { /* next tree element detected */
          done = true;
          endLineIndex = i - 1; /* un-read line */
        }
      }
      
      return {
        level: level,
        expanded: undefined,
        content: multiLines,
        endLineIndex: endLineIndex
      };
    };
    
    var buildTreeElementsImpl = function() {
      var lines = inputTreeText.split(/\r?\n/);
      var treeElements = [];
      var index = 0;
      
      while (index < lines.length) {
        if (lines[index].trim()) { /* not empty */
          var precedingTreeElement = (treeElements.length > 0) ? treeElements[treeElements.length - 1] : undefined;
          var treeElement = getTreeElement(lines, index, precedingTreeElement);
          
          treeElements.push(treeElement);
          index = treeElement.endLineIndex + 1;
        }
        else {
          index++;
        }
      }
      
      return treeElements;
    };
    
    return buildTreeElementsImpl();
  };
  
  /* Module function, generating output */
  var outputTree = function(treeElements, htmlPrintln, allElementsAreFolders, rootLevelBold, nonFoldersItalic)
  {
    var hasChildren = function(treeElements, i) {
      if (i >= treeElements.length - 1)
        return false;
        
      return treeElements[i].level < treeElements[i + 1].level;
    };
    
    var printTree = function(treeElements) {
      var INITIAL_LEVEL = 1; /* minimal number of leading dashes */
      var level = INITIAL_LEVEL;
      
      for (var i = 0; i < treeElements.length; i++) {
        var treeElement = treeElements[i];
        
        for (; level > treeElement.level; level--) /* close preceding element */
          htmlPrintln("</details>");
        
        level = treeElement.level;
        
        var isFolder = hasChildren(treeElements, i);
        if (isFolder || (allElementsAreFolders && treeElement.expanded != undefined))
          htmlPrintln(
              "<details "+
              (treeElement.expanded ? "open" : "")+
              " class=\"tree\"><summary>"+
              treeElement.content+
              "</summary>"+
              (isFolder ? "" : "</details>"));
        else
          htmlPrintln(
              "<div class=\"tree\">"+
              treeElement.content+
              "</div>");
      }
      
      for ( ; level > INITIAL_LEVEL; level--)
        htmlPrintln("</details>");
    };
    
    var printTreeStyles = function() {
      htmlPrintln("<style>");
      htmlPrintln("  details.tree {");
      htmlPrintln("    padding-left: 1em;");
      if (rootLevelBold)
        htmlPrintln("    font-weight: bold;");
      htmlPrintln("  }");
      htmlPrintln("  div.tree {");
      htmlPrintln("    padding-left: 2.06em;");
      if (nonFoldersItalic)
        htmlPrintln("    font-style: italic;");
      if (rootLevelBold)
        htmlPrintln("    font-weight: bold;");
      htmlPrintln("  }");
      if (rootLevelBold) {
        htmlPrintln("  details.tree details.tree, details.tree div.tree {");
        htmlPrintln("    font-weight: normal;");
        htmlPrintln("  }");
      }
      htmlPrintln("</style>");
    };
    
    var printHtml5Tree = function() {
      htmlPrintln("<div>");
      printTreeStyles();
      printTree(treeElements);
      htmlPrintln("</div>");
    };
    
    printHtml5Tree();
  };
  
  
  /* Global variables */
  var outputTextarea = document.getElementById("output-tree");
  var sample = document.getElementById("sample");
  
  /* "Generate" callback function */
  var generate = function() {
    var inputTreeArea = document.getElementById("input-tree");
    var inputTreeText = inputTreeArea.value.trim();
    var expandedLevelCharacter = document.getElementById("expandedLevelCharacter").value;
    var collapsedLevelCharacter = document.getElementById("collapsedLevelCharacter").value;
    
    try {
      var treeElements = buildTreeElements(inputTreeText, expandedLevelCharacter, collapsedLevelCharacter);
      outputTextarea.value = ""; /* clear output */
      
      var htmlPrintln = function(htmlText) {
        var currentText = outputTextarea.value;
        outputTextarea.value = (currentText ? currentText+"\n" : "")+htmlText
      };
      
      var allElementsAreFolders = document.getElementById("allElementsAreFolders").checked;
      var rootLevelBold = document.getElementById("rootLevelBold").checked;
      var nonFoldersItalic = document.getElementById("nonFoldersItalic").checked;
      
      outputTree(treeElements, htmlPrintln, allElementsAreFolders, rootLevelBold, nonFoldersItalic);
      
      toHtml();
    }
    catch (error) {
      alert(error);
    }
  };
  
  /* "Display" callback function */
  var toHtml = function() {
    sample.innerHTML =  outputTextarea.value;
  };
  
  /* "Copy" callback function */
  var copyToClipboard = function() {
    document.getElementById('output-tree').select();
    document.execCommand('copy');
  };
  

Resume

Unfortunately <details> elements are not animated. Implementing animated tree expansion with JavaScript/CSS is quite complex.

Another problem might be that it can't be configured whether a click on the <summary> text should or should not open the details. Some users may prefer to click onto the text without opening the tree.