@@ -118,20 +118,58 @@ target.Extension is null ||
118118 return ( results , cleanup ) ;
119119 }
120120
121- // Target contains an arbitrary object - resolve it
121+ // Target contains an arbitrary object - resolve it using full type matching
122122 var data = target . Data ;
123123 if ( data is null )
124124 {
125125 return ( results , cleanup ) ;
126126 }
127127
128- // Handle Stream without extension
129- if ( data is Stream stream2 )
128+ // Handle XContainer (XDocument, XElement)
129+ if ( data is XContainer container )
130130 {
131- results . Add ( new ResolvedTarget ( "bin" , stream2 , target . Name ) ) ;
131+ var xmlString = ConvertXmlToString ( container ) ;
132+ results . Add ( new ResolvedTarget ( "xml" , xmlString , target . Name ) ) ;
132133 return ( results , cleanup ) ;
133134 }
134135
136+ // Handle XmlNode
137+ if ( data is XmlNode node )
138+ {
139+ using var reader = new XmlNodeReader ( node ) ;
140+ reader . MoveToContent ( ) ;
141+ var xdoc = XDocument . Load ( reader ) ;
142+ var xmlString = ConvertXmlToString ( xdoc ) ;
143+ results . Add ( new ResolvedTarget ( "xml" , xmlString , target . Name ) ) ;
144+ return ( results , cleanup ) ;
145+ }
146+
147+ // Handle FileStream (get extension from filename)
148+ if ( data is FileStream fileStream )
149+ {
150+ var extension = fileStream . Extension ( ) ;
151+ return await ResolveStream ( fileStream , extension , target . Name ) ;
152+ }
153+
154+ // Handle Stream
155+ if ( data is Stream stream2 )
156+ {
157+ return await ResolveStream ( stream2 , "bin" , target . Name ) ;
158+ }
159+
160+ // Handle byte[]
161+ if ( data is byte [ ] bytes )
162+ {
163+ var memStream = new MemoryStream ( bytes ) ;
164+ return await ResolveStream ( memStream , "bin" , target . Name ) ;
165+ }
166+
167+ // Handle IEnumerable<Stream> - throw error as in Verify(object)
168+ if ( data is IEnumerable < Stream > )
169+ {
170+ throw new ( "Use Verify(IEnumerable<T> targets, string extension)" ) ;
171+ }
172+
135173 // Handle StringBuilder
136174 if ( data is StringBuilder sb2 )
137175 {
@@ -146,6 +184,15 @@ target.Extension is null ||
146184 return ( results , cleanup ) ;
147185 }
148186
187+ // Try ToString converter (before typed converter, matching Verify(object) order)
188+ if ( VerifierSettings . TryGetToString ( data , out var toString ) )
189+ {
190+ var stringResult = toString ( data , settings . Context ) ;
191+ var extension = stringResult . Extension ?? "txt" ;
192+ results . Add ( new ResolvedTarget ( extension , stringResult . Value , target . Name ) ) ;
193+ return ( results , cleanup ) ;
194+ }
195+
149196 // Try typed converter
150197 if ( VerifierSettings . TryGetTypedConverter ( data , settings , out var converter ) )
151198 {
@@ -174,22 +221,143 @@ target.Extension is null ||
174221 return ( results , cleanup ) ;
175222 }
176223
177- // Try ToString converter
178- if ( VerifierSettings . TryGetToString ( data , out var toString ) )
179- {
180- var stringResult = toString ( data , settings . Context ) ;
181- var extension = stringResult . Extension ?? "txt" ;
182- results . Add ( new ResolvedTarget ( extension , stringResult . Value , target . Name ) ) ;
183- return ( results , cleanup ) ;
184- }
185-
186224 // Fall back to JSON serialization
187225 results . Add ( new ResolvedTarget (
188226 settings . TxtOrJson ,
189227 JsonFormatter . AsJson ( settings , counter , data ) ) ) ;
190228 return ( results , cleanup ) ;
191229 }
192230
231+ async Task < ( List < ResolvedTarget > targets , Func < Task > cleanup ) > ResolveStream ( Stream stream , string extension , string ? name )
232+ {
233+ var cleanup = ( ) => Task . CompletedTask ;
234+ var results = new List < ResolvedTarget > ( ) ;
235+
236+ stream . MoveToStart ( ) ;
237+
238+ // Check for stream converter
239+ if ( VerifierSettings . HasStreamConverter ( extension ) )
240+ {
241+ var ( info , converted , convCleanup ) = await DoExtensionConversion ( extension , stream , null , name ) ;
242+ cleanup += convCleanup ;
243+
244+ // Add info target if present
245+ if ( info != null )
246+ {
247+ results . Add ( new ResolvedTarget (
248+ settings . TxtOrJson ,
249+ JsonFormatter . AsJson ( settings , counter , info ) ) ) ;
250+ }
251+
252+ // Recursively resolve converted targets (they may contain objects)
253+ foreach ( var convTarget in converted )
254+ {
255+ var ( resolved , resolveCleanup ) = await ResolveTarget ( convTarget ) ;
256+ cleanup += resolveCleanup ;
257+ results . AddRange ( resolved ) ;
258+ }
259+
260+ return ( results , cleanup ) ;
261+ }
262+
263+ // No converter - convert stream directly to ResolvedTarget
264+ if ( FileExtensions . IsTextExtension ( extension ) )
265+ {
266+ results . Add ( new ResolvedTarget ( extension , await stream . ReadStringBuilderWithFixedLines ( ) , name ) ) ;
267+ }
268+ else
269+ {
270+ results . Add ( new ResolvedTarget ( extension , stream , name ) ) ;
271+ }
272+
273+ return ( results , cleanup ) ;
274+ }
275+
276+ string ConvertXmlToString ( XContainer target )
277+ {
278+ var serialization = settings . serialization ;
279+ var elements = target
280+ . Descendants ( )
281+ . ToList ( ) ;
282+
283+ foreach ( var element in elements )
284+ {
285+ if ( serialization . TryGetScrubOrIgnoreByName ( element . Name . LocalName , out var scrubOrIgnore ) )
286+ {
287+ if ( scrubOrIgnore == ScrubOrIgnore . Ignore )
288+ {
289+ element . Remove ( ) ;
290+ }
291+ else
292+ {
293+ element . Value = "Scrubbed" ;
294+ }
295+
296+ continue ;
297+ }
298+
299+ ScrubXmlAttributes ( element , serialization ) ;
300+ }
301+
302+ foreach ( var node in target . DescendantNodes ( ) )
303+ {
304+ switch ( node )
305+ {
306+ case XText text :
307+ text . Value = ConvertXmlValue ( text . Value ) ;
308+ continue ;
309+ case XComment comment :
310+ comment . Value = ConvertXmlValue ( comment . Value ) ;
311+ continue ;
312+ }
313+ }
314+
315+ return target . ToString ( ) ;
316+ }
317+
318+ string ConvertXmlValue ( string value )
319+ {
320+ var span = value . AsSpan ( ) ;
321+ if ( counter . TryConvert ( span , out var result ) )
322+ {
323+ return result ;
324+ }
325+
326+ return ApplyScrubbers . ApplyForPropertyValue ( span , settings , counter ) . ToString ( ) ;
327+ }
328+
329+ void ScrubXmlAttributes ( XElement node , SerializationSettings serialization )
330+ {
331+ foreach ( var attribute in node
332+ . Attributes ( )
333+ . ToList ( ) )
334+ {
335+ if ( serialization . TryGetScrubOrIgnoreByName ( attribute . Name . LocalName , out var scrubOrIgnore ) )
336+ {
337+ if ( scrubOrIgnore == ScrubOrIgnore . Ignore )
338+ {
339+ attribute . Remove ( ) ;
340+ }
341+ else
342+ {
343+ attribute . Value = "Scrubbed" ;
344+ }
345+
346+ continue ;
347+ }
348+
349+ var span = attribute . Value . AsSpan ( ) ;
350+ if ( counter . TryConvert ( span , out var result ) )
351+ {
352+ attribute . Value = result ;
353+ }
354+ else
355+ {
356+ attribute . Value = ApplyScrubbers . ApplyForPropertyValue ( span , settings , counter ) . ToString ( ) ;
357+ }
358+ }
359+ }
360+
193361 bool TryGetRootTarget ( object ? root , bool ignoreNullRoot , [ NotNullWhen ( true ) ] out ResolvedTarget ? target )
194362 {
195363 var appends = VerifierSettings . GetJsonAppenders ( settings ) ;
0 commit comments