commit
ba4f367be8
6 changed files with 706 additions and 27 deletions
48
DesignTools/Primitives/ComponentObject3D.cs
Normal file
48
DesignTools/Primitives/ComponentObject3D.cs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
Copyright (c) 2018, Lars Brubaker, John Lewin
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
The views and conclusions contained in the software and documentation are those
|
||||
of the authors and should not be interpreted as representing official policies,
|
||||
either expressed or implied, of the FreeBSD Project.
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using MatterHackers.DataConverters3D;
|
||||
|
||||
namespace MatterHackers.MatterControl.DesignTools
|
||||
{
|
||||
public class ComponentObject3D : Object3D
|
||||
{
|
||||
public ComponentObject3D()
|
||||
{
|
||||
}
|
||||
|
||||
public ComponentObject3D(IEnumerable<IObject3D> children)
|
||||
: base (children)
|
||||
{
|
||||
}
|
||||
|
||||
public List<string> SurfacedEditors { get; set; } = new List<string>();
|
||||
}
|
||||
}
|
||||
|
|
@ -27,14 +27,11 @@ of the authors and should not be interpreted as representing official policies,
|
|||
either expressed or implied, of the FreeBSD Project.
|
||||
*/
|
||||
|
||||
using System.Threading;
|
||||
using MatterHackers.Agg;
|
||||
using MatterHackers.Agg.UI;
|
||||
using MatterHackers.Agg.VertexSource;
|
||||
using MatterHackers.DataConverters3D;
|
||||
using MatterHackers.Localizations;
|
||||
using MatterHackers.MatterControl.DesignTools.Operations;
|
||||
using MatterHackers.PolygonMesh;
|
||||
using MatterHackers.VectorMath;
|
||||
|
||||
namespace MatterHackers.MatterControl.DesignTools
|
||||
|
|
|
|||
|
|
@ -51,11 +51,14 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
public class EditableProperty
|
||||
{
|
||||
public IObject3D Item { get; private set; }
|
||||
|
||||
public object source;
|
||||
public PropertyInfo PropertyInfo { get; private set; }
|
||||
|
||||
public EditableProperty(PropertyInfo p, IObject3D item)
|
||||
public EditableProperty(PropertyInfo p, object source)
|
||||
{
|
||||
this.Item = item;
|
||||
this.source = source;
|
||||
this.Item = source as IObject3D;
|
||||
this.PropertyInfo = p;
|
||||
}
|
||||
|
||||
|
|
@ -71,7 +74,7 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
return nameAttribute?.DisplayName ?? prop.Name.SplitCamelCase();
|
||||
}
|
||||
|
||||
public object Value => PropertyInfo.GetGetMethod().Invoke(Item, null);
|
||||
public object Value => PropertyInfo.GetGetMethod().Invoke(source, null);
|
||||
|
||||
/// <summary>
|
||||
/// Use reflection to set property value
|
||||
|
|
@ -79,7 +82,7 @@ namespace MatterHackers.MatterControl.DesignTools
|
|||
/// <param name="value"></param>
|
||||
public void SetValue(object value)
|
||||
{
|
||||
this.PropertyInfo.GetSetMethod().Invoke(this.Item, new Object[] { value });
|
||||
this.PropertyInfo.GetSetMethod().Invoke(source, new Object[] { value });
|
||||
}
|
||||
|
||||
public string DisplayName => GetDisplayName(PropertyInfo);
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@
|
|||
<Compile Include="DesignTools\Operations\Image\MapOnMaxIntensity.cs" />
|
||||
<Compile Include="DesignTools\Operations\OperationSource.cs" />
|
||||
<Compile Include="DesignTools\Operations\PinchObject3D.cs" />
|
||||
<Compile Include="DesignTools\Primitives\ComponentObject3D.cs" />
|
||||
<Compile Include="DesignTools\Primitives\CubeObject3D.cs" />
|
||||
<Compile Include="DesignTools\Primitives\HalfCylinderObject3D.cs" />
|
||||
<Compile Include="DesignTools\Primitives\HalfWedgeObject3D.cs" />
|
||||
|
|
@ -425,6 +426,7 @@
|
|||
<Compile Include="SlicerConfiguration\Settings\PrinterSettings.cs" />
|
||||
<Compile Include="DataStorage\Classic\ClassicSqlitePrinterProfiles.cs" />
|
||||
<Compile Include="Utilities\AuthenticationData.cs" />
|
||||
<Compile Include="Utilities\JsonPath.cs" />
|
||||
<Compile Include="Utilities\LimitCallFrequency.cs" />
|
||||
<Compile Include="Utilities\SelectedListItems.cs" />
|
||||
<Compile Include="EeProm\EePromMarlinSettings.cs" />
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ either expressed or implied, of the FreeBSD Project.
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using JsonPath;
|
||||
using MatterHackers.Agg;
|
||||
using MatterHackers.Agg.Platform;
|
||||
using MatterHackers.Agg.UI;
|
||||
|
|
@ -39,6 +41,7 @@ using MatterHackers.MatterControl.CustomWidgets;
|
|||
using MatterHackers.MatterControl.DesignTools;
|
||||
using MatterHackers.MatterControl.Library;
|
||||
using MatterHackers.VectorMath;
|
||||
using static JsonPath.JsonPathContext.ReflectionValueSystem;
|
||||
|
||||
namespace MatterHackers.MatterControl.PartPreviewWindow
|
||||
{
|
||||
|
|
@ -280,9 +283,10 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
|
||||
public GuiWidget ContentPanel { get; set; }
|
||||
|
||||
JsonPathContext xpathLikeResolver = new JsonPathContext();
|
||||
|
||||
public void SetActiveItem(IObject3D selectedItem)
|
||||
{
|
||||
|
||||
var selectedItemType = selectedItem.GetType();
|
||||
|
||||
editButton.Enabled = (selectedItem.Children.Count > 0);
|
||||
|
|
@ -295,7 +299,54 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
|
||||
var activeEditors = new List<(IObject3DEditor, IObject3D, string)>();
|
||||
|
||||
foreach (var child in new[] { selectedItem })
|
||||
var editableItems = new List<IObject3D> { selectedItem };
|
||||
|
||||
var undoBuffer = sceneContext.Scene.UndoBuffer;
|
||||
|
||||
editorPanel.CloseAllChildren();
|
||||
|
||||
bool allowOperations = true;
|
||||
|
||||
if (selectedItem is ComponentObject3D componentObject)
|
||||
{
|
||||
allowOperations = false;
|
||||
|
||||
foreach (var selector in componentObject.SurfacedEditors)
|
||||
{
|
||||
// Get the named property via reflection
|
||||
// Selector example: '$.Children<CylinderObject3D>'
|
||||
var match = xpathLikeResolver.Select(componentObject, selector).ToList();
|
||||
|
||||
//// TODO: Create editor row for each property
|
||||
//// - Use the type of the property to find a matching editor (ideally all datatype -> editor functionality would resolve consistently)
|
||||
//// - Add editor row for each
|
||||
|
||||
foreach (var instance in match)
|
||||
{
|
||||
if (instance is IObject3D object3D)
|
||||
{
|
||||
editableItems.Add(object3D);
|
||||
}
|
||||
else if (JsonPath.JsonPathContext.ReflectionValueSystem.LastMemberValue is ReflectionTarget reflectionTarget)
|
||||
{
|
||||
var context = new PPEContext()
|
||||
{
|
||||
item = item
|
||||
};
|
||||
|
||||
var editableProperty = new EditableProperty(reflectionTarget.PropertyInfo, reflectionTarget.Source);
|
||||
|
||||
var editor = PublicPropertyEditor.CreatePropertyEditor(editableProperty, undoBuffer, context, theme);
|
||||
if (editor != null)
|
||||
{
|
||||
editorPanel.AddChild(editor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var child in editableItems)
|
||||
{
|
||||
if (ApplicationController.Instance.GetEditorsForType(child.GetType())?.FirstOrDefault() is IObject3DEditor editor)
|
||||
{
|
||||
|
|
@ -303,7 +354,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
}
|
||||
}
|
||||
|
||||
ShowObjectEditor(activeEditors, selectedItem);
|
||||
ShowObjectEditor(activeEditors, selectedItem, allowOperations: allowOperations);
|
||||
}
|
||||
|
||||
private class OperationButton :TextButton
|
||||
|
|
@ -324,10 +375,8 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
}
|
||||
}
|
||||
|
||||
private void ShowObjectEditor(IEnumerable<(IObject3DEditor editor, IObject3D item, string displayName)> scope, IObject3D rootSelection)
|
||||
private void ShowObjectEditor(IEnumerable<(IObject3DEditor editor, IObject3D item, string displayName)> scope, IObject3D rootSelection, bool allowOperations = true)
|
||||
{
|
||||
editorPanel.CloseAllChildren();
|
||||
|
||||
if (scope == null)
|
||||
{
|
||||
return;
|
||||
|
|
@ -366,25 +415,28 @@ namespace MatterHackers.MatterControl.PartPreviewWindow
|
|||
|
||||
var buttons = new List<OperationButton>();
|
||||
|
||||
foreach (var nodeOperation in ApplicationController.Instance.Graph.Operations)
|
||||
if (allowOperations)
|
||||
{
|
||||
foreach (var type in nodeOperation.MappedTypes)
|
||||
foreach (var nodeOperation in ApplicationController.Instance.Graph.Operations)
|
||||
{
|
||||
if (type.IsAssignableFrom(selectedItemType)
|
||||
&& (nodeOperation.IsVisible == null || nodeOperation.IsVisible(selectedItem)))
|
||||
foreach (var type in nodeOperation.MappedTypes)
|
||||
{
|
||||
var button = new OperationButton(nodeOperation, selectedItem, theme)
|
||||
if (type.IsAssignableFrom(selectedItemType)
|
||||
&& (nodeOperation.IsVisible == null || nodeOperation.IsVisible(selectedItem)))
|
||||
{
|
||||
BackgroundColor = theme.MinimalShade,
|
||||
Margin = theme.ButtonSpacing
|
||||
};
|
||||
button.EnsureAvailablity();
|
||||
button.Click += (s, e) =>
|
||||
{
|
||||
nodeOperation.Operation(selectedItem, sceneContext.Scene).ConfigureAwait(false);
|
||||
};
|
||||
var button = new OperationButton(nodeOperation, selectedItem, theme)
|
||||
{
|
||||
BackgroundColor = theme.MinimalShade,
|
||||
Margin = theme.ButtonSpacing
|
||||
};
|
||||
button.EnsureAvailablity();
|
||||
button.Click += (s, e) =>
|
||||
{
|
||||
nodeOperation.Operation(selectedItem, sceneContext.Scene).ConfigureAwait(false);
|
||||
};
|
||||
|
||||
buttons.Add(button);
|
||||
buttons.Add(button);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
577
Utilities/JsonPath.cs
Normal file
577
Utilities/JsonPath.cs
Normal file
|
|
@ -0,0 +1,577 @@
|
|||
#region Copyright (c) 2007 Atif Aziz. All rights reserved.
|
||||
// https://github.com/atifaziz/JSONPath
|
||||
//
|
||||
// C# implementation of JSONPath[1]
|
||||
// [1] http://goessner.net/articles/JsonPath/
|
||||
//
|
||||
// The MIT License
|
||||
//
|
||||
// Copyright (c) 2007 Atif Aziz . All rights reserved.
|
||||
// Portions Copyright (c) 2007 Stefan Goessner (goessner.net)
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
//
|
||||
#endregion
|
||||
|
||||
namespace JsonPath
|
||||
{
|
||||
#region Imports
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using MatterHackers.DataConverters3D;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
#endregion
|
||||
|
||||
public interface IJsonPathValueSystem
|
||||
{
|
||||
bool HasMember(object value, string member);
|
||||
object GetMemberValue(object value, string member);
|
||||
IEnumerable<string> GetMembers(object value);
|
||||
bool IsObject(object value);
|
||||
bool IsArray(object value);
|
||||
bool IsPrimitive(object value);
|
||||
}
|
||||
|
||||
public sealed class JsonPathContext
|
||||
{
|
||||
public static readonly JsonPathContext Default = new JsonPathContext();
|
||||
|
||||
public Func<string /* script */,
|
||||
object /* value */,
|
||||
string /* context */,
|
||||
object /* result */>
|
||||
ScriptEvaluator { get; set; }
|
||||
|
||||
public IJsonPathValueSystem ValueSystem { get; set; }
|
||||
|
||||
public IEnumerable<object> Select(object obj, string expr) =>
|
||||
SelectNodes(obj, expr, (v, _) => v);
|
||||
|
||||
public IEnumerable<T> SelectNodes<T>(object obj, string expr, Func<object, string, T> resultor)
|
||||
{
|
||||
if (obj == null) throw new ArgumentNullException(nameof(obj));
|
||||
if (resultor == null) throw new ArgumentNullException(nameof(resultor));
|
||||
|
||||
var i = new Interpreter(ValueSystem, ScriptEvaluator);
|
||||
|
||||
expr = Normalize(expr);
|
||||
|
||||
if (expr.Length >= 1 && expr[0] == '$') // ^\$:?
|
||||
expr = expr.Substring(expr.Length >= 2 && expr[1] == ';' ? 2 : 1);
|
||||
|
||||
return i.Trace(expr, obj, "$", (value, path) => resultor(value, AsBracketNotation(path)));
|
||||
}
|
||||
|
||||
static string Normalize(string expr)
|
||||
{
|
||||
var subx = new List<string>();
|
||||
expr = RegExp.Replace(expr, @"[\['](\??\(.*?\))[\]']", m =>
|
||||
{
|
||||
subx.Add(m.Groups[1].Value);
|
||||
return "[#" + (subx.Count - 1).ToString(CultureInfo.InvariantCulture) + "]";
|
||||
});
|
||||
expr = RegExp.Replace(expr, @"'?\.'?|\['?", ";");
|
||||
expr = RegExp.Replace(expr, @";;;|;;", ";..;");
|
||||
expr = RegExp.Replace(expr, @";$|'?\]|'$", string.Empty);
|
||||
expr = RegExp.Replace(expr, @"#([0-9]+)", m =>
|
||||
{
|
||||
var index = int.Parse(m.Groups[1].Value, CultureInfo.InvariantCulture);
|
||||
return subx[index];
|
||||
});
|
||||
return expr;
|
||||
}
|
||||
|
||||
public static string AsBracketNotation(string[] indicies)
|
||||
{
|
||||
if (indicies == null)
|
||||
throw new ArgumentNullException(nameof(indicies));
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
foreach (var index in indicies)
|
||||
{
|
||||
if (sb.Length == 0)
|
||||
{
|
||||
sb.Append('$');
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append('[');
|
||||
if (RegExp.IsMatch(index, @"^[0-9*]+$"))
|
||||
sb.Append(index);
|
||||
else
|
||||
sb.Append('\'').Append(index).Append('\'');
|
||||
sb.Append(']');
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
static int? TryParseInt(string str) =>
|
||||
int.TryParse(str, NumberStyles.Integer, CultureInfo.InvariantCulture, out var n)
|
||||
? n : (int?) null;
|
||||
|
||||
sealed class Interpreter
|
||||
{
|
||||
readonly Func<string, object, string, object> _eval;
|
||||
readonly IJsonPathValueSystem _system;
|
||||
|
||||
static readonly IJsonPathValueSystem DefaultValueSystem = new ReflectionValueSystem(); // new BasicValueSystem();
|
||||
|
||||
static readonly char[] Colon = { ':' };
|
||||
static readonly char[] Semicolon = { ';' };
|
||||
|
||||
delegate void WalkCallback(object member, string loc, string expr, object value, string path);
|
||||
|
||||
public Interpreter(IJsonPathValueSystem valueSystem, Func<string, object, string, object> eval)
|
||||
{
|
||||
_eval = eval ?? delegate
|
||||
{
|
||||
// @ symbol in expr must be interpreted specially to resolve
|
||||
// to value. In JavaScript, the implementation would look
|
||||
// like:
|
||||
//
|
||||
// return obj && value && eval(expr.replace(/@/g, "value"));
|
||||
|
||||
return null;
|
||||
};
|
||||
_system = valueSystem ?? DefaultValueSystem;
|
||||
}
|
||||
|
||||
sealed class TraceArgs
|
||||
{
|
||||
public readonly string Expr;
|
||||
public readonly object Value;
|
||||
public readonly string Path;
|
||||
|
||||
public TraceArgs(string expr, object value, string path)
|
||||
{
|
||||
Expr = expr;
|
||||
Value = value;
|
||||
Path = path;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<T> Trace<T>(string expr, object value, string path, Func<object, string[], T> resultor) =>
|
||||
Trace(Args(expr, value, path), resultor);
|
||||
|
||||
static TraceArgs Args(string expr, object value, string path) =>
|
||||
new TraceArgs(expr, value, path);
|
||||
|
||||
IEnumerable<T> Trace<T>(TraceArgs args, Func<object, string[], T> resultor)
|
||||
{
|
||||
var stack = new Stack<TraceArgs>();
|
||||
stack.Push(args);
|
||||
|
||||
while (stack.Count > 0)
|
||||
{
|
||||
var popped = stack.Pop();
|
||||
var expr = popped.Expr;
|
||||
var value = popped.Value;
|
||||
var path = popped.Path;
|
||||
|
||||
if (string.IsNullOrEmpty(expr))
|
||||
{
|
||||
if (path != null)
|
||||
yield return resultor(value, path.Split(Semicolon));
|
||||
continue;
|
||||
}
|
||||
|
||||
var i = expr.IndexOf(';');
|
||||
var atom = i >= 0 ? expr.Substring(0, i) : expr;
|
||||
var tail = i >= 0 ? expr.Substring(i + 1) : string.Empty;
|
||||
|
||||
if (value != null && _system.HasMember(value, atom))
|
||||
{
|
||||
stack.Push(Args(tail, Index(value, atom), path + ";" + atom));
|
||||
}
|
||||
else if (atom == "*")
|
||||
{
|
||||
Walk(atom, tail, value, path, (m, l, x, v, p) => stack.Push(Args(m + ";" + x, v, p)));
|
||||
}
|
||||
else if (atom == "..")
|
||||
{
|
||||
Walk(atom, tail, value, path, (m, l, x, v, p) =>
|
||||
{
|
||||
var result = Index(v, m.ToString());
|
||||
if (result != null && !_system.IsPrimitive(result))
|
||||
stack.Push(Args("..;" + x, result, p + ";" + m));
|
||||
});
|
||||
stack.Push(Args(tail, value, path));
|
||||
}
|
||||
else if (atom.Length > 2 && atom[0] == '(' && atom[atom.Length - 1] == ')') // [(exp)]
|
||||
{
|
||||
stack.Push(Args(_eval(atom, value, path.Substring(path.LastIndexOf(';') + 1)) + ";" + tail, value, path));
|
||||
}
|
||||
else if (atom.Length > 3 && atom[0] == '?' && atom[1] == '(' && atom[atom.Length - 1] == ')') // [?(exp)]
|
||||
{
|
||||
Walk(atom, tail, value, path, (m, l, x, v, p) =>
|
||||
{
|
||||
var result = _eval(RegExp.Replace(l, @"^\?\((.*?)\)$", "$1"),
|
||||
Index(v, m.ToString()), m.ToString());
|
||||
|
||||
if (Convert.ToBoolean(result, CultureInfo.InvariantCulture))
|
||||
stack.Push(Args(m + ";" + x, v, p));
|
||||
});
|
||||
}
|
||||
else if (RegExp.IsMatch(atom, @"^(-?[0-9]*):(-?[0-9]*):?([0-9]*)$")) // [start:end:step] Phyton slice syntax
|
||||
{
|
||||
foreach (var a in Slice(atom, tail, value, path).Reverse())
|
||||
stack.Push(a);
|
||||
}
|
||||
else if (atom.IndexOf(',') >= 0) // [name1,name2,...]
|
||||
{
|
||||
foreach (var part in RegExp.Split(atom, @"'?,'?").Reverse())
|
||||
stack.Push(Args(part + ";" + tail, value, path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Walk(string loc, string expr, object value, string path, WalkCallback callback)
|
||||
{
|
||||
if (_system.IsPrimitive(value))
|
||||
return;
|
||||
|
||||
if (_system.IsArray(value))
|
||||
{
|
||||
var list = (IList) value;
|
||||
for (var i = list.Count - 1; i >= 0; i--)
|
||||
callback(i, loc, expr, value, path);
|
||||
}
|
||||
else if (_system.IsObject(value))
|
||||
{
|
||||
foreach (var key in _system.GetMembers(value).Reverse())
|
||||
callback(key, loc, expr, value, path);
|
||||
}
|
||||
}
|
||||
|
||||
static IEnumerable<TraceArgs> Slice(string loc, string expr, object value, string path)
|
||||
{
|
||||
if (!(value is IList list))
|
||||
yield break;
|
||||
|
||||
var length = list.Count;
|
||||
var parts = loc.Split(Colon);
|
||||
var start = TryParseInt(parts[0]) ?? 0;
|
||||
var end = TryParseInt(parts[1]) ?? list.Count;
|
||||
var step = parts.Length > 2 ? TryParseInt(parts[2]) ?? 1 : 1;
|
||||
start = (start < 0) ? Math.Max(0, start + length) : Math.Min(length, start);
|
||||
end = (end < 0) ? Math.Max(0, end + length) : Math.Min(length, end);
|
||||
for (var i = start; i < end; i += step)
|
||||
yield return Args(i + ";" + expr, value, path);
|
||||
}
|
||||
|
||||
object Index(object obj, string member) =>
|
||||
_system.GetMemberValue(obj, member);
|
||||
}
|
||||
|
||||
static class RegExp
|
||||
{
|
||||
const RegexOptions Options = RegexOptions.ECMAScript;
|
||||
|
||||
public static bool IsMatch(string input, string pattern) =>
|
||||
Regex.IsMatch(input, pattern, Options);
|
||||
|
||||
public static string Replace(string input, string pattern, string replacement) =>
|
||||
Regex.Replace(input, pattern, replacement, Options);
|
||||
|
||||
public static string Replace(string input, string pattern, MatchEvaluator evaluator) =>
|
||||
Regex.Replace(input, pattern, evaluator, Options);
|
||||
|
||||
public static IEnumerable<string> Split(string input, string pattern) =>
|
||||
Regex.Split(input, pattern, Options);
|
||||
}
|
||||
|
||||
public class ReflectionDetails
|
||||
{
|
||||
public List<PropertyInfo> Properties { get; set; }
|
||||
}
|
||||
|
||||
public class ReflectionValueSystem : IJsonPathValueSystem
|
||||
{
|
||||
//private static Dictionary<string, ReflectionDetails> members = new Dictionary<string, ReflectionDetails>();
|
||||
|
||||
public const BindingFlags PublicInstanceProperties = BindingFlags.Public | BindingFlags.Instance;
|
||||
|
||||
public static ReflectionDetails GetMember(object item, string path)
|
||||
{
|
||||
//if (members.TryGetValue(path, out ReflectionDetails memberInfo))
|
||||
//{
|
||||
// return memberInfo;
|
||||
//}
|
||||
|
||||
var reflectionDetails = new ReflectionDetails()
|
||||
{
|
||||
Properties = item.GetType().GetProperties(PublicInstanceProperties).Where(pi => pi.GetGetMethod() != null && pi.GetSetMethod() != null).ToList()
|
||||
};
|
||||
|
||||
// members[path] = reflectionDetails;
|
||||
|
||||
return reflectionDetails;
|
||||
}
|
||||
|
||||
public class ReflectionTarget
|
||||
{
|
||||
public PropertyInfo PropertyInfo { get; }
|
||||
public object Source { get; }
|
||||
|
||||
public ReflectionTarget(PropertyInfo propertyInfo, object source)
|
||||
{
|
||||
this.PropertyInfo = propertyInfo;
|
||||
this.Source = source;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasMember(object value, string member)
|
||||
{
|
||||
if (!IsPrimitive(value)
|
||||
&& value is IDictionary dict)
|
||||
{
|
||||
return dict.Contains(member);
|
||||
}
|
||||
|
||||
if (value is IList list)
|
||||
{
|
||||
return TryParseInt(member) is int i
|
||||
&& i >= 0
|
||||
&& i < list.Count;
|
||||
}
|
||||
|
||||
string typeFilter;
|
||||
|
||||
// Separate member and typeFilter from member field
|
||||
(member, typeFilter) = StripTypeFilter(member);
|
||||
|
||||
// IEnumerable field must be iterated to check
|
||||
if (value is IEnumerable enumerable)
|
||||
{
|
||||
if (TryParseInt(member) is int)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle the typeFilter case
|
||||
foreach (var n in enumerable)
|
||||
{
|
||||
if (n.GetType().Name == typeFilter)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Inline once troubleshooting is complete
|
||||
var hasMember = GetMember(value, member).Properties.Any(p => p.Name == member);
|
||||
return hasMember;
|
||||
}
|
||||
|
||||
public static ReflectionTarget LastMemberValue { get; private set; }
|
||||
|
||||
public object GetMemberValue(object value, string member)
|
||||
{
|
||||
// Find and invoke property to get value
|
||||
LastMemberValue = new ReflectionTarget(
|
||||
GetMember(value, member).Properties.Where(p => p.Name == member).FirstOrDefault(),
|
||||
value);
|
||||
|
||||
if (IsPrimitive(value))
|
||||
{
|
||||
throw new ArgumentException(null, nameof(value));
|
||||
}
|
||||
|
||||
if (value is IDictionary dict)
|
||||
{
|
||||
return dict[member];
|
||||
}
|
||||
|
||||
if (value is IList list
|
||||
&& TryParseInt(member) is int i
|
||||
&& i >= 0
|
||||
&& i < list.Count)
|
||||
{
|
||||
return list[i];
|
||||
}
|
||||
|
||||
string typeFilter;
|
||||
|
||||
// Separate member and typeFilter from member field
|
||||
(member, typeFilter) = StripTypeFilter(member);
|
||||
|
||||
var propertyInfo = GetMember(value, member).Properties.Where(p => p.Name == member).FirstOrDefault();
|
||||
|
||||
LastMemberValue = new ReflectionTarget(propertyInfo, value);
|
||||
|
||||
if (value is IEnumerable enumerable)
|
||||
{
|
||||
if (TryParseInt(member) is int k)
|
||||
{
|
||||
var v = 0;
|
||||
foreach (var n in enumerable)
|
||||
{
|
||||
if (v++ == k)
|
||||
{
|
||||
return n;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var n in enumerable)
|
||||
{
|
||||
if (n.GetType().Name == typeFilter)
|
||||
{
|
||||
return n;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var propertyValue = propertyInfo.GetGetMethod().Invoke(value, null);
|
||||
|
||||
if (!string.IsNullOrEmpty(typeFilter) && propertyValue is IEnumerable items)
|
||||
{
|
||||
foreach(var item in items)
|
||||
{
|
||||
if (item.GetType().Name == typeFilter)
|
||||
{
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return propertyValue;
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetMembers(object value) =>
|
||||
((IDictionary)value).Keys.Cast<string>();
|
||||
|
||||
public bool IsObject(object value) => value is IDictionary;
|
||||
public bool IsArray(object value) => value is IList;
|
||||
|
||||
public bool IsPrimitive(object value) =>
|
||||
value == null
|
||||
? throw new ArgumentNullException(nameof(value))
|
||||
: Type.GetTypeCode(value.GetType()) != TypeCode.Object;
|
||||
|
||||
private static (string member, string filter) StripTypeFilter(string member)
|
||||
{
|
||||
int startFilter = member.IndexOf('<');
|
||||
int endFilter = member.IndexOf('>');
|
||||
int length = endFilter - startFilter;
|
||||
string typeFilter = "";
|
||||
|
||||
if (startFilter != -1 && endFilter != -1 && length > 1)
|
||||
{
|
||||
typeFilter = member.Substring(startFilter + 1, length - 1);
|
||||
|
||||
member = member.Substring(0, startFilter);
|
||||
}
|
||||
|
||||
return (member, typeFilter);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sealed class BasicValueSystem : IJsonPathValueSystem
|
||||
{
|
||||
public bool HasMember(object value, string member)
|
||||
{
|
||||
if (!IsPrimitive(value)
|
||||
&& value is IDictionary dict)
|
||||
{
|
||||
return dict.Contains(member);
|
||||
}
|
||||
|
||||
if (value is JArray array)
|
||||
{
|
||||
return TryParseInt(member) is int j
|
||||
&& j >= 0
|
||||
&& j < array.Count;
|
||||
}
|
||||
|
||||
if (value is JToken token)
|
||||
{
|
||||
return token[member] != null;
|
||||
}
|
||||
|
||||
return value is IList list
|
||||
&& TryParseInt(member) is int i
|
||||
&& i >= 0
|
||||
&& i < list.Count;
|
||||
}
|
||||
|
||||
public object GetMemberValue(object value, string member)
|
||||
{
|
||||
if (IsPrimitive(value))
|
||||
{
|
||||
throw new ArgumentException(null, nameof(value));
|
||||
}
|
||||
|
||||
if (value is JArray array
|
||||
&& TryParseInt(member) is int j
|
||||
&& j >= 0
|
||||
&& j < array.Count)
|
||||
{
|
||||
return array[j];
|
||||
}
|
||||
|
||||
if (value is JToken token)
|
||||
{
|
||||
return token[member];
|
||||
}
|
||||
|
||||
if (value is IDictionary dict)
|
||||
{
|
||||
return dict[member];
|
||||
}
|
||||
|
||||
if (!(value is IList list))
|
||||
{
|
||||
throw new ArgumentException(nameof(value));
|
||||
}
|
||||
|
||||
if (TryParseInt(member) is int i
|
||||
&& i >= 0
|
||||
&& i < list.Count)
|
||||
{
|
||||
return list[i];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetMembers(object value) =>
|
||||
((IDictionary) value).Keys.Cast<string>();
|
||||
|
||||
public bool IsObject(object value) => value is IDictionary;
|
||||
public bool IsArray(object value) => value is IList;
|
||||
|
||||
public bool IsPrimitive(object value) =>
|
||||
value == null
|
||||
? throw new ArgumentNullException(nameof(value))
|
||||
: Type.GetTypeCode(value.GetType()) != TypeCode.Object;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue