11using System . Collections ;
22using System . Diagnostics . CodeAnalysis ;
3+ using System . Text . Json . Nodes ;
34using OpenCvSharp . Internal ;
45using OpenCvSharp . Internal . Vectors ;
56
@@ -45,6 +46,28 @@ private void InitSafeHandle(IntPtr p, bool ownsHandle = true)
4546 static h => NativeMethods . HandleException ( NativeMethods . core_FileNode_delete ( h ) ) ) ) ;
4647 }
4748
49+ /// <summary>
50+ /// Wraps a native cv::FileNode* as a nullable FileNode, normalizing the "not found" case.
51+ /// Native FileNode lookups (FileStorage/FileNode indexers) never return a null pointer -
52+ /// a missing key comes back as a non-null, heap-allocated node whose Type is None. That
53+ /// sentinel is indistinguishable from a key that is present but explicitly stores a "none"
54+ /// value (e.g. YAML's <c>~</c>), so this treats both as "not found" and returns null,
55+ /// which is what a C# caller chaining with <c>?.</c> actually expects.
56+ /// </summary>
57+ internal static FileNode ? FromRawPtrOrNull ( IntPtr ptr )
58+ {
59+ if ( ptr == IntPtr . Zero )
60+ return null ;
61+
62+ var node = new FileNode ( ptr ) ;
63+ if ( node . IsNone )
64+ {
65+ node . Dispose ( ) ;
66+ return null ;
67+ }
68+ return node ;
69+ }
70+
4871 #endregion
4972
5073 #region Cast
@@ -74,6 +97,31 @@ public int ToInt32()
7497 return ret ;
7598 }
7699
100+ /// <summary>
101+ /// Returns the node content as a signed 64-bit integer. If the node stores a floating-point number, it is rounded.
102+ /// </summary>
103+ /// <param name="node"></param>
104+ /// <returns></returns>
105+ public static explicit operator long ( FileNode node )
106+ {
107+ ArgumentNullException . ThrowIfNull ( node ) ;
108+ return node . ToInt64 ( ) ;
109+ }
110+
111+ /// <summary>
112+ /// Returns the node content as a signed 64-bit integer. If the node stores a floating-point number, it is rounded.
113+ /// </summary>
114+ /// <returns></returns>
115+ public long ToInt64 ( )
116+ {
117+ ThrowIfDisposed ( ) ;
118+
119+ NativeMethods . HandleException (
120+ NativeMethods . core_FileNode_toInt64 ( Handle , out var ret ) ) ;
121+
122+ return ret ;
123+ }
124+
77125 /// <summary>
78126 /// Returns the node content as float
79127 /// </summary>
@@ -176,12 +224,65 @@ public Mat ToMat()
176224 return matrix ;
177225 }
178226
227+ /// <summary>
228+ /// Converts this node (and, recursively, its children) into a <see cref="JsonNode"/> tree,
229+ /// regardless of whether the underlying <see cref="FileStorage"/> was opened as XML, YAML or
230+ /// JSON. This is a generic structural conversion (scalars, sequences, mappings) - it does not
231+ /// give special treatment to OpenCV-specific encodings such as <c>Mat</c>/<c>KeyPoint</c>/
232+ /// <c>DMatch</c>, which come through as their raw mapping/sequence shape (e.g. a Mat becomes a
233+ /// JSON object with "rows"/"cols"/"dt"/"data" members, not an <see cref="OpenCvSharp.Mat"/>).
234+ /// The result can be fed to <see cref="System.Text.Json.JsonSerializer"/> or queried directly.
235+ /// </summary>
236+ /// <returns>
237+ /// The converted node, or null for a <see cref="Types.None"/> node (mirroring JSON <c>null</c>).
238+ /// </returns>
239+ public JsonNode ? ToJsonNode ( )
240+ {
241+ ThrowIfDisposed ( ) ;
242+
243+ return Type switch
244+ {
245+ Types . None => null ,
246+ Types . Int => JsonValue . Create ( ToInt64 ( ) ) ,
247+ Types . Real => JsonValue . Create ( ToDouble ( ) ) ,
248+ Types . Str => JsonValue . Create ( ToString ( ) ) ,
249+ Types . Seq => SeqToJsonArray ( ) ,
250+ Types . Map => MapToJsonObject ( ) ,
251+ _ => throw new NotSupportedException ( $ "Cannot convert a FileNode of type { Type } to a JsonNode.") ,
252+ } ;
253+ }
254+
255+ private JsonArray SeqToJsonArray ( )
256+ {
257+ var array = new JsonArray ( ) ;
258+ foreach ( var child in this )
259+ {
260+ using ( child )
261+ {
262+ array . Add ( child . ToJsonNode ( ) ) ;
263+ }
264+ }
265+ return array ;
266+ }
267+
268+ private JsonObject MapToJsonObject ( )
269+ {
270+ var obj = new JsonObject ( ) ;
271+ foreach ( var key in Keys )
272+ {
273+ using var child = this [ key ] ;
274+ obj [ key ] = child ? . ToJsonNode ( ) ;
275+ }
276+ return obj ;
277+ }
278+
179279 #endregion
180280
181281 #region Properties
182282
183283 /// <summary>
184- /// returns element of a mapping node
284+ /// Returns the element of a mapping node with the given key, or null if the key is not
285+ /// present (or is explicitly stored as a "none" value).
185286 /// </summary>
186287 public FileNode ? this [ string nodeName ]
187288 {
@@ -193,28 +294,69 @@ public FileNode? this[string nodeName]
193294 NativeMethods . HandleException (
194295 NativeMethods . core_FileNode_operatorThis_byString ( Handle , nodeName , out var node ) ) ;
195296
196- if ( node == IntPtr . Zero )
197- return null ;
198- return new FileNode ( node ) ;
297+ return FromRawPtrOrNull ( node ) ;
199298 }
200299 }
201300
202301 /// <summary>
203- /// returns element of a sequence node
302+ /// Returns the element of a sequence node at the given index, or null if the index is out
303+ /// of range (or the element is explicitly stored as a "none" value).
204304 /// </summary>
205305 public FileNode ? this [ int i ]
206306 {
207307 get
208308 {
209309 ThrowIfDisposed ( ) ;
210310
211- NativeMethods . HandleException (
311+ NativeMethods . HandleException (
212312 NativeMethods . core_FileNode_operatorThis_byInt ( Handle , i , out var node ) ) ;
213313
214- if ( node == IntPtr . Zero )
314+ return FromRawPtrOrNull ( node ) ;
315+ }
316+ }
317+
318+ /// <summary>
319+ /// Navigates a chain of mapping keys (<see cref="string"/>) and/or sequence indices
320+ /// (<see cref="int"/>), disposing every intermediate <see cref="FileNode"/> along the way.
321+ /// Equivalent to repeated indexer chaining (e.g. <c>node["a"][2]["b"]</c>), except that the
322+ /// indexer chain leaves every intermediate node unreferenced - each one is still a real
323+ /// native allocation that would otherwise sit around until the GC finalizes it.
324+ /// </summary>
325+ /// <param name="path">One or more mapping keys / sequence indices to follow, in order.</param>
326+ /// <returns>The node at the end of the path, or null if any segment along the way is missing.</returns>
327+ public FileNode ? GetPath ( params object [ ] path )
328+ {
329+ ArgumentNullException . ThrowIfNull ( path ) ;
330+ if ( path . Length == 0 )
331+ throw new ArgumentException ( "Path must contain at least one key or index." , nameof ( path ) ) ;
332+
333+ ThrowIfDisposed ( ) ;
334+
335+ var current = this ;
336+ var ownsCurrent = false ;
337+
338+ foreach ( var segment in path )
339+ {
340+ var next = segment switch
341+ {
342+ string key => current [ key ] ,
343+ int index => current [ index ] ,
344+ _ => throw new ArgumentException (
345+ $ "Path segments must be string (mapping key) or int (sequence index), got '{ segment ? . GetType ( ) } '.",
346+ nameof ( path ) ) ,
347+ } ;
348+
349+ if ( ownsCurrent )
350+ current . Dispose ( ) ;
351+
352+ if ( next is null )
215353 return null ;
216- return new FileNode ( node ) ;
354+
355+ current = next ;
356+ ownsCurrent = true ;
217357 }
358+
359+ return current ;
218360 }
219361
220362 /// <summary>
@@ -368,6 +510,35 @@ public long Size
368510 }
369511 }
370512
513+ /// <summary>
514+ /// Returns the keys of a mapping node.
515+ /// </summary>
516+ public string [ ] Keys
517+ {
518+ get
519+ {
520+ ThrowIfDisposed ( ) ;
521+ using var buf = new VectorOfString ( ) ;
522+ NativeMethods . HandleException (
523+ NativeMethods . core_FileNode_keys ( Handle , buf . CvPtr ) ) ;
524+ return buf . ToArray ( ) ;
525+ }
526+ }
527+
528+ /// <summary>
529+ /// Returns the raw size of the node in bytes.
530+ /// </summary>
531+ public long RawSize
532+ {
533+ get
534+ {
535+ ThrowIfDisposed ( ) ;
536+ NativeMethods . HandleException (
537+ NativeMethods . core_FileNode_rawSize ( Handle , out var ret ) ) ;
538+ return ret . ToInt64 ( ) ;
539+ }
540+ }
541+
371542 /// <summary>
372543 /// Returns type of the node.
373544 /// </summary>
@@ -433,14 +604,17 @@ IEnumerator IEnumerable.GetEnumerator()
433604 /// Reads node elements to the buffer with the specified format
434605 /// </summary>
435606 /// <param name="fmt"></param>
436- /// <param name="vec"></param>
437- /// <param name="len"></param>
438- public void ReadRaw ( string fmt , IntPtr vec , long len )
607+ /// <param name="vec">The buffer to read into.</param>
608+ public unsafe void ReadRaw ( string fmt , Span < byte > vec )
439609 {
440610 ThrowIfDisposed ( ) ;
441611 ArgumentNullException . ThrowIfNull ( fmt ) ;
442- NativeMethods . HandleException (
443- NativeMethods . core_FileNode_readRaw ( Handle , fmt , vec , new IntPtr ( len ) ) ) ;
612+
613+ fixed ( byte * p = vec )
614+ {
615+ NativeMethods . HandleException (
616+ NativeMethods . core_FileNode_readRaw ( Handle , fmt , ( IntPtr ) p , new IntPtr ( vec . Length ) ) ) ;
617+ }
444618 }
445619
446620 #region Read
@@ -457,6 +631,18 @@ public int ReadInt(int defaultValue = default)
457631 return value ;
458632 }
459633
634+ /// <summary>
635+ /// Reads the node element as Int64 (long)
636+ /// </summary>
637+ /// <param name="defaultValue"></param>
638+ /// <returns></returns>
639+ public long ReadInt64 ( long defaultValue = default )
640+ {
641+ NativeMethods . HandleException (
642+ NativeMethods . core_FileNode_read_int64 ( Handle , out var value , defaultValue ) ) ;
643+ return value ;
644+ }
645+
460646 /// <summary>
461647 /// Reads the node element as Single (float)
462648 /// </summary>
0 commit comments