Skip to content

Commit

Permalink
Fix bugs; rename tree view when file renamed
Browse files Browse the repository at this point in the history
ADD:
* When a file is renamed, the name of a tree view associated with that file
    also changes to match the new name.
FIX:
* Fix minor bugs with how headers are formatted
    in the `s_csv` RemesPath function and JSON-to-CSV form
* Fix bug where renaming a file subject to schema validation based on filename patterns
    would cause its tree view to be lost.
* Fix bug where plugin actions (mainly RemesPath queries in regex mode)
    that set the text of the entire document to an empty string would not do anything.
    Those actions will now correctly remove all the text in the document.
  • Loading branch information
molsonkiko committed Sep 11, 2024
1 parent 18bf739 commit d249410
Show file tree
Hide file tree
Showing 16 changed files with 226 additions and 143 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

### Added

1. When a file is renamed, the name of a [tree view](/docs/README.md#json-tools-overview) associated with that file also changes to match the new name.

### Changed

### Fixed
Expand All @@ -65,6 +67,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
2. Fix issue where RemesPath incorrectly inferred the type of (a [function](/docs/RemesPath.md#functions) `fun` followed by [indexers](/docs/RemesPath.md#indexing-and-selecting-keys)) to be the return type of `fun`. For example, running the query `sum(dict(items(@)).a)` on the JSON `{"a": [1]}` now correctly returns `1.0`, but RemesPath *used to raise an error because it assumed that `dict(items(@)).a` had the same type as `dict(items(@))`*
3. Fix very rare crash bug when using the `Value to clipboard` option of the [tree node right-click context menu](/docs/README.md#get-info-about-tree-nodes).
4. Fix bug where some invalid JSON Lines documents (for example, `[1, \n2][3]`) would be accepted by the [JSON Lines parser](/docs/README.md#json-lines-documents) despite having elements that span multiple lines.
5. Fix minor bugs with how headers are formatted in [the `s_csv` RemesPath function](/docs/RemesPath.md#vectorized-functions) and [JSON-to-CSV form](/docs/README.md#json-to-csv).
6. Fix bug where renaming a file subject to [schema validation based on filename patterns](/docs/README.md#automatic-validation-of-json-against-json-schema) would cause its tree view to be lost.
7. Fix bug where plugin actions (mainly RemesPath queries in [regex mode](/docs/README.md#regex-search-form)) that set the text of the entire document to an empty string would not do anything. Those actions will now correctly remove all the text in the document.

## [8.1.0] - 2024-08-23

Expand Down
15 changes: 14 additions & 1 deletion JsonToolsNppPlugin/Forms/ErrorForm.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions JsonToolsNppPlugin/Forms/ErrorForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public partial class ErrorForm : Form
public string fname;
public List<JsonLint> lints;
private bool isRepopulatingErrorGrid;
public IntPtr ptrNppTbData = IntPtr.Zero;
public IntPtr ptrTitleBuf = IntPtr.Zero;

public ErrorForm(string fname, List<JsonLint> lints)
{
Expand Down
15 changes: 14 additions & 1 deletion JsonToolsNppPlugin/Forms/TreeViewer.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

50 changes: 38 additions & 12 deletions JsonToolsNppPlugin/Forms/TreeViewer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using JSON_Tools.JSON_Tools;
using JSON_Tools.Utils;
using Kbg.NppPluginNET;
using Kbg.NppPluginNET.PluginInfrastructure;

namespace JSON_Tools.Forms
{
Expand All @@ -23,6 +25,15 @@ public partial class TreeViewer : Form
/// </summary>
public string fname;

private int MAX_LEN_TITLE_BUFFER = 256;

/// <summary>
/// a pointer to the buffer containing the name displayed at the top of this docking form
/// </summary>
public IntPtr ptrTitleBuf = IntPtr.Zero;

public IntPtr ptrNppTbData = IntPtr.Zero;

/// <summary>
/// Maps TreeNode.FullPath to the TreeNode's corresponding JNode
/// </summary>
Expand Down Expand Up @@ -1454,25 +1465,40 @@ private void DocumentTypeComboBox_SelectedIndexChanged(object sender, EventArgs
}

/// <summary>
/// Just the filename, no directory information.<br></br>
/// If no fname supplied, gets the relative filename for this TreeViewer's fname.
/// Change the fname attribute of this.<br></br>
/// Also change the title of the UI element (the docking form that the user actually sees)
/// </summary>
public string RelativeFilename(string fname = null)
/// <param name="newFname"></param>
public void Rename(string newFname, bool isFilename)
{
if (fname == null) fname = this.fname;
string[] fnameSplit = fname.Split('\\');
return fnameSplit[fnameSplit.Length - 1];
fname = newFname;
SetTitleBuffer(newFname, isFilename);
Marshal.WriteIntPtr(ptrNppTbData, IntPtr.Size, ptrTitleBuf);
Win32.SendMessage(PluginBase.nppData._nppHandle, (uint)NppMsg.NPPM_DMMUPDATEDISPINFO, 0, Handle);
}

/// <summary>
/// Change the fname attribute of this.<br></br>
/// We would like to be able to change the title of the UI element as well,
/// but it seems pretty hard to do from C#.
/// sets this.ptrTitleBuf to a pointer to an unmanaged Unicode char array containing the title of this TreeViewer's docking form.<br></br>
/// If isFilename, strips the directory name away from the beginning of title.
/// </summary>
/// <param name="newFname"></param>
public void Rename(string newFname)
public IntPtr SetTitleBuffer(string title, bool isFilename)
{
fname = newFname;
if (isFilename)
{
string[] fnameSplit = fname.Split('\\');
title = fnameSplit[fnameSplit.Length - 1];
}
string defaultNameFormat = "Json Tree View for {0}";
string nameFormat = (Translator.TryGetTranslationAtPath(new string[] { "forms", "TreeViewer", "title" }, out JNode node) && node.value is string s && s.Contains("{0}")) ? s : defaultNameFormat;
string fullTitle = nameFormat.Replace("{0}", title);
int maxCharsTitleBuf = MAX_LEN_TITLE_BUFFER / Marshal.SystemDefaultCharSize - 1;
if (fullTitle.Length > maxCharsTitleBuf)
fullTitle = fullTitle.Substring(0, maxCharsTitleBuf - 3) + "...";
if (ptrTitleBuf == IntPtr.Zero)
ptrTitleBuf = Marshal.AllocHGlobal(MAX_LEN_TITLE_BUFFER);
Marshal.Copy(new byte[MAX_LEN_TITLE_BUFFER], 0, ptrTitleBuf, MAX_LEN_TITLE_BUFFER);
Marshal.Copy(fullTitle.ToCharArray(), 0, ptrTitleBuf, fullTitle.Length);
return ptrTitleBuf;
}
}
}
95 changes: 47 additions & 48 deletions JsonToolsNppPlugin/JSONTools/JsonTabularize.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@ public JsonFormat outputFormat
}

public JsonTabularizer(JsonTabularizerStrategy strategy = JsonTabularizerStrategy.DEFAULT) // ,
// JsonFormat outputFormat = JsonFormat.REC)
// don't worry about outputFormat, because other output formats aren't supported
// JsonFormat outputFormat = JsonFormat.REC)
// don't worry about outputFormat, because other output formats aren't supported
{
this.strategy = strategy;
//if ((outputFormat & JsonFormat.ANY_TABLE) != 0) _outputFormat = outputFormat;
Expand Down Expand Up @@ -446,14 +446,14 @@ private void AnyTableToRecord(JNode obj,
}
notArrDict = ResolveHang(notArrDict, keySep);
if (lenArr == 0)
{
{
// A 0-length array should be dealt with by adding a single row with an empty value
// for the array's parent key
foreach (string k in arrDict.Keys)
notArrDict[k] = new JNode("", Dtype.STR, 0);
result.Add(new JObject(0, notArrDict));
return;
}
}
for (int ii = 0; ii < lenArr; ii++)
{
Dictionary<string, JNode> newrec = new Dictionary<string, JNode>(restOfRow);
Expand Down Expand Up @@ -650,7 +650,7 @@ private void BuildTableHelper_FULL_RECURSIVE(JNode obj,
private void BuildTableHelper_STRINGIFY_ITERABLES(JNode obj, List<JNode> result, string keySep = ".")
{
if (obj is JArray)
{
{
foreach (JNode child in ((JArray)obj).children)
{
Dictionary<string, JNode> row = new Dictionary<string, JNode>();
Expand Down Expand Up @@ -724,16 +724,16 @@ private void BuildTableHelper_STRINGIFY_ITERABLES(JNode obj, List<JNode> result,
// are filled by empty strings
if (ii >= v.Length)
newrec[k] = new JNode("", Dtype.STR, 0);
else
{
else
{
JNode subchild = v[ii];
if (subchild is JArray subarr)
newrec[k] = new JNode(subarr.ToString(), Dtype.STR, 0);
else if (subchild is JObject subobj)
newrec[k] = new JNode(subobj.ToString(), Dtype.STR, 0);
else
newrec[k] = subchild;
}
}
}
// now add in all the non-array values
foreach (string k in notArrDict.Keys)
Expand Down Expand Up @@ -795,64 +795,64 @@ public JArray BuildTable(JNode obj, Dictionary<string, object> schema, string ke
return new JArray(0, result);
}

/// <summary>
/// If string s contains the delimiter, '\r', '\n', or a literal quote character, append (the string wrapped in quotes) to sb.<br></br>
/// If s contains literal quote character, it is escaped by doubling it up according to the CSV RFC 4180 (https://www.ietf.org/rfc/rfc4180.txt)<br></br>
/// Otherwise, append s to sb unchanged
/// </summary>
/// <param name="delim">CSV delimiter ('\x00' if not specified)</param>
/// <param name="quote">CSV quote char ('\x00' if not specified)</param>
/// <returns></returns>
public static void ApplyQuotesIfNeeded(StringBuilder sb, string s, char delim, char quote)
/// <summary>
/// If string s contains the delimiter, '\r', '\n', or a literal quote character, append (the string wrapped in quotes) to sb.<br></br>
/// If s contains literal quote character, it is escaped by doubling it up according to the CSV RFC 4180 (https://www.ietf.org/rfc/rfc4180.txt)<br></br>
/// Otherwise, append s to sb unchanged
/// </summary>
/// <param name="delim">CSV delimiter ('\x00' if not specified)</param>
/// <param name="quote">CSV quote char ('\x00' if not specified)</param>
/// <returns></returns>
public static void ApplyQuotesIfNeeded(StringBuilder sb, string s, char delim, char quote)
{
if (s.IndexOfAny(new char[] {delim, '\r', '\n', quote}) >= 0)
if (s.IndexOfAny(new char[] { delim, '\r', '\n', quote }) >= 0)
{
sb.Append(quote);
for (int ii = 0; ii < s.Length; ii++)
{
char c = s[ii];
sb.Append(c);
if (c == quote)
sb.Append(quote);
sb.Append(quote);
}
sb.Append(quote);
}
else sb.Append(s);
}

/// <summary>
/// append the CSV (RFC 4180 adherent, using quote character = quote, delimiter = delim) representation of jnode's value to sb.
/// </summary>
/// <param name="delim">CSV delimiter ('\x00' if not specified)</param>
/// <param name="quote">CSV quote char ('\x00' if not specified)</param>
/// <param name="boolsAsInts">if true, represent true as "1" and false as "0". If false, represent them as "true" and "false" respectively</param>
public static void CsvStringToSb(StringBuilder sb, JNode jnode, char delim, char quote, bool boolsAsInts)
/// <summary>
/// append the CSV (RFC 4180 adherent, using quote character = quote, delimiter = delim) representation of jnode's value to sb.
/// </summary>
/// <param name="delim">CSV delimiter ('\x00' if not specified)</param>
/// <param name="quote">CSV quote char ('\x00' if not specified)</param>
/// <param name="boolsAsInts">if true, represent true as "1" and false as "0". If false, represent them as "true" and "false" respectively</param>
public static void CsvStringToSb(StringBuilder sb, JNode jnode, char delim, char quote, bool boolsAsInts)
{
string val;
switch (jnode.type)
{
case Dtype.STR:
val = (string)jnode.value;
break; // only apply quotes if internal delims, quotes, or newlines
//case Dtype.DATE:
// val = ((DateTime)jnode.value).ToString("yyyy-MM-dd");
// break;
//case Dtype.DATETIME:
// val = ((DateTime)jnode.value).ToString("yyyy-MM-dd hh:mm:ss");
// break;
case Dtype.NULL:
return; // nulls should be empty entries
case Dtype.BOOL:
switch (jnode.type)
{
case Dtype.STR:
val = (string)jnode.value;
break; // only apply quotes if internal delims, quotes, or newlines
//case Dtype.DATE:
// val = ((DateTime)jnode.value).ToString("yyyy-MM-dd");
// break;
//case Dtype.DATETIME:
// val = ((DateTime)jnode.value).ToString("yyyy-MM-dd hh:mm:ss");
// break;
case Dtype.NULL:
return; // nulls should be empty entries
case Dtype.BOOL:
sb.Append((bool)jnode.value
? (boolsAsInts ? "1" : "true")
: (boolsAsInts ? "0" : "false"));
return;
default:
val = jnode.ToString();
break;
}
return;
default:
val = jnode.ToString();
break;
}
ApplyQuotesIfNeeded(sb, val, delim, quote);
}
}

public string TableToCsv(JArray table, char delim = ',', char quote = '"', string newline = "\n", string[] header = null, bool boolsAsInts = false)
{
Expand All @@ -878,8 +878,7 @@ public string TableToCsv(JArray table, char delim = ',', char quote = '"', strin
StringBuilder sb = new StringBuilder();
for (int ii = 0; ii < header.Length; ii++)
{
string col = header[ii];
ApplyQuotesIfNeeded(sb, JNode.StrToString(col, false), delim, quote);
ApplyQuotesIfNeeded(sb, header[ii], delim, quote);
if (ii < header.Length - 1) sb.Append(delim);
}
sb.Append(newline);
Expand Down
26 changes: 21 additions & 5 deletions JsonToolsNppPlugin/JSONTools/RemesPathFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2735,6 +2735,24 @@ private static void CacheResultsOfRegexSearch(string input, Regex rex, HeaderHan
regexSearchResultCache[(input, argsAsJArrayString)] = output;
}

/// <summary>
/// Unquote a string that is quoted according to RFC 4180 conventions, where str is enclosed in quoteChars and each literal quoteChar is doubled.<br></br>
/// If str is not wrapped in quoteChars, or if quoteChar is '\x00', return str.
/// EXAMPLES:<br></br>
/// * <c>UnquoteCsvQuotedString("|foo||bar|", '|', "|", "||")</c> would return <c>"foo|bar"</c><br></br>
/// * <c>UnquoteCsvQuotedString("^^^quz^", '^', "^", "^^")</c> would return <c>"^quz"</c><br></br>
/// * <c>UnquoteCsvQuotedString("bllo", '"', "\"", "\"\"")</c> would return <c>"bllo"</c> (because it's not wrapped in quotes)<br></br>
/// * <c>UnquoteCsvQuotedString("^bllo^", '\x00', "\x00", "\x00\x00")</c> would return <c>"bllo"</c> (because quoteChar is '\x00')
/// </summary>
/// <param name="quoteStr">unless quoteChar == '\x00', must be new string(quoteChar, 1)</param>
/// <param name="doubleQuoteStr">unless quoteChar == '\x00', must be new string(quoteChar, 2)</param>
private static string UnquoteCsvQuotedString(string str, char quoteChar, string quoteStr, string doubleQuoteStr)
{
if (quoteChar > 0 && str.Length > 0 && str[0] == quoteChar)
return str.Substring(1, str.Length - 2).Replace(doubleQuoteStr, quoteStr);
return str;
}

/// <summary>
/// return an array of strings(if rex has 0 or 1 capture group(s)) or an array of arrays of strings (if there are multiple capture groups)<br></br>
/// The arguments in args at index firstOptionalArgNum onward will be treated as the 0-based indices of capture groups to parse as numbers<br></br>
Expand Down Expand Up @@ -2772,9 +2790,7 @@ JNode matchEvaluator(string mValue, bool tryParseAsNumber, int jnodePosition)
parsed.value = parsedStr.Substring(1, parsedStr.Length - 2).Replace(doubleQuoteStr, quoteStr);
return parsed;
}
if (csvQuoteChar > 0 && mValue.Length > 0 && mValue[0] == csvQuoteChar)
return new JNode(mValue.Substring(1, mValue.Length - 2).Replace(doubleQuoteStr, quoteStr), jnodePosition);
return new JNode(mValue, jnodePosition);
return new JNode(UnquoteCsvQuotedString(mValue, csvQuoteChar, quoteStr, doubleQuoteStr), jnodePosition);
}
int minGroupNum = headerHandling == HeaderHandlingInCsv.INCLUDE_FULL_MATCH_AS_FIRST_ITEM ? 0 : 1;
int nColumns = minGroupNum >= maxGroupNum ? 1 : maxGroupNum + 1 - minGroupNum;
Expand Down Expand Up @@ -2828,7 +2844,7 @@ JNode matchEvaluator(string mValue, bool tryParseAsNumber, int jnodePosition)
{
if (headerHandling == HeaderHandlingInCsv.MAP_HEADER_TO_ROWS)
{
header = new List<string> { mValue };
header = new List<string> { UnquoteCsvQuotedString(mValue, csvQuoteChar, quoteStr, doubleQuoteStr) };
}
}
if (parseMatchesAsRow)
Expand All @@ -2852,7 +2868,7 @@ JNode matchEvaluator(string mValue, bool tryParseAsNumber, int jnodePosition)
{
header = new List<string>(nColumns);
for (int ii = minGroupNum; ii <= maxGroupNum; ii++)
header.Add(m.Groups[ii].Value);
header.Add(UnquoteCsvQuotedString(m.Groups[ii].Value, csvQuoteChar, quoteStr, doubleQuoteStr));
}
}
if (parseMatchesAsRow)
Expand Down
Loading

0 comments on commit d249410

Please sign in to comment.