@@ -343,7 +343,7 @@ private static bool CompareElements(XDocument original, XDocument modified, XEle
343343 Logger . Debug ( $ "Comparing text in element '{ originalElem . Name } ': '{ originalText } ' vs '{ modifiedText } '") ;
344344 if ( originalText != modifiedText )
345345 {
346- string sel = GenerateXPath ( originalElem , original . Root ) ;
346+ string sel = GenerateXPath ( originalElem ) ;
347347 if ( ! string . IsNullOrEmpty ( modifiedText ) )
348348 {
349349 if ( checkOnly ) {
@@ -412,7 +412,7 @@ private static bool CompareElements(XDocument original, XDocument modified, XEle
412412 matchedEnough = false ;
413413 break ;
414414 }
415- string sel = $ "{ GenerateXPath ( originalChild , original . Root ) } ";
415+ string sel = $ "{ GenerateXPath ( originalChild ) } ";
416416 savedOp = new XElement ( "add" ,
417417 new XAttribute ( "sel" , sel ) ,
418418 new XAttribute ( "type" , $ "@{ attr . Key } ") ,
@@ -429,7 +429,7 @@ private static bool CompareElements(XDocument original, XDocument modified, XEle
429429 matchedEnough = false ;
430430 break ;
431431 }
432- string sel = $ "{ GenerateXPath ( originalChild , original . Root ) } /@{ attr . Key } ";
432+ string sel = $ "{ GenerateXPath ( originalChild ) } /@{ attr . Key } ";
433433 savedOp = new XElement ( "replace" ,
434434 new XAttribute ( "sel" , sel ) ,
435435 attr . Value
@@ -501,17 +501,17 @@ private static bool CompareElements(XDocument original, XDocument modified, XEle
501501 if ( originalChild . Name == nextModifiedChild . Name &&
502502 originalChild . Attributes ( ) . All ( attr => nextModifiedChild . Attribute ( attr . Name ) ? . Value == attr . Value ) )
503503 {
504+ XElement addOp = new XElement ( "add" ,
505+ new XAttribute ( "sel" , GenerateXPath ( originalChild ) ) ,
506+ new XAttribute ( "pos" , "before" )
507+ ) ;
504508 for ( int l = j ; l < k ; l ++ )
505509 {
506510 var addedChild = modifiedChildren [ l ] ;
507- XElement addOp = new XElement ( "add" ,
508- new XAttribute ( "sel" , GenerateXPath ( originalChild , originalChild . Document . Root ) ) ,
509- new XAttribute ( "pos" , "before" ) ,
510- addedChild
511- ) ;
512- diffRoot . Add ( addOp ) ;
511+ addOp . Add ( addedChild ) ;
513512 Logger . Debug ( $ "Added element '{ addedChild . Name } ' to parent '{ originalElem . Name } '.") ;
514513 }
514+ diffRoot . Add ( addOp ) ;
515515 j = k ;
516516 foundMatch = true ;
517517 break ;
@@ -520,7 +520,7 @@ private static bool CompareElements(XDocument original, XDocument modified, XEle
520520
521521 if ( ! foundMatch )
522522 {
523- string sel = GenerateXPath ( originalChild , original . Root ) ;
523+ string sel = GenerateXPath ( originalChild ) ;
524524 XElement removeOp = new XElement ( "remove" ,
525525 new XAttribute ( "sel" , sel )
526526 ) ;
@@ -538,7 +538,7 @@ private static bool CompareElements(XDocument original, XDocument modified, XEle
538538 while ( i < originalChildren . Count )
539539 {
540540 var originalChild = originalChildren [ i ] ;
541- string sel = GenerateXPath ( originalChild , original . Root ) ;
541+ string sel = GenerateXPath ( originalChild ) ;
542542 XElement removeOp = new XElement ( "remove" ,
543543 new XAttribute ( "sel" , sel )
544544 ) ;
@@ -547,73 +547,83 @@ private static bool CompareElements(XDocument original, XDocument modified, XEle
547547 i ++ ;
548548 }
549549
550- while ( j < modifiedChildren . Count )
550+ if ( j + 1 < modifiedChildren . Count )
551551 {
552- var addedChild = modifiedChildren [ j ] ;
553- XElement addOp = new XElement ( "add" ,
554- new XAttribute ( "sel" , GenerateXPath ( addedChild , original . Root ) ) ,
555- new XAttribute ( "pos" , "after" ) ,
556- addedChild
557- ) ;
558- diffRoot . Add ( addOp ) ;
559- Logger . Debug ( $ "Added element '{ addedChild . Name } ' to parent '{ originalElem . Name } '.") ;
560- j ++ ;
552+ XElement ? originalLast = originalChildren . LastOrDefault ( ) ;
553+ if ( originalLast != null ) {
554+ XElement addOp = new XElement ( "add" ,
555+ new XAttribute ( "sel" , GenerateXPath ( originalLast ) ) ,
556+ new XAttribute ( "pos" , "after" )
557+ ) ;
558+ while ( j < modifiedChildren . Count )
559+ {
560+ var addedChild = modifiedChildren [ j ] ;
561+ addOp . Add ( addedChild ) ;
562+ Logger . Debug ( $ "Added element '{ addedChild . Name } ' to parent '{ originalElem . Name } '.") ;
563+ j ++ ;
564+ }
565+ diffRoot . Add ( addOp ) ;
566+ }
561567 }
562568 return false ;
563569 }
564570
565- private static string GenerateXPath ( XElement element , XElement ? root )
571+ private static string GenerateXPath ( XElement element )
566572 {
567- if ( element == null || root == null )
573+ if ( element == null )
574+ return string . Empty ;
575+ XElement ? root = element . Document ? . Root ;
576+ if ( root == null )
568577 return string . Empty ;
569578
579+ // Attempt to make // with a unique attribute
580+ foreach ( var attr in element . Attributes ( ) )
581+ {
582+ string attrValue = attr . Value . Replace ( "\" " , """ ) ;
583+ string xpath = $ "//{ element . Name . LocalName } [@{ attr . Name . LocalName } =\" { attrValue } \" ]";
584+ var matches = root . XPathSelectElements ( xpath ) ;
585+ if ( matches . Count ( ) == 1 )
586+ return xpath ;
587+ }
588+
570589 var path = new System . Text . StringBuilder ( ) ;
571590 XElement ? current = element ;
572591 while ( current != null )
573592 {
574593 string step = current . Name . LocalName ;
575- var siblings = current . Parent ? . Elements ( current . Name . LocalName ) . ToList ( ) ;
576- if ( siblings != null && siblings . Count > 1 )
577- {
578- // Attempt to use a unique attribute
579- var uniqueAttr = siblings
580- . SelectMany ( e => e . Attributes ( ) )
581- . GroupBy ( a => a . Name . LocalName )
582- . Where ( g => g . Count ( ) == siblings . Count ( ) )
583- . Select ( g => g . Key )
584- . FirstOrDefault ( ) ;
585-
586- if ( uniqueAttr != null && current . Attribute ( uniqueAttr ) != null )
594+ XElement ? parent = current . Parent ;
595+ if ( parent != null ) {
596+ var siblings = parent . Elements ( current . Name . LocalName ) . ToList ( ) ;
597+ if ( siblings != null && siblings . Count > 1 )
587598 {
588- string value = current . Attribute ( uniqueAttr ) ! . Value . Replace ( "\" " , """ ) ;
589- step += $ "[@{ uniqueAttr } =\" { value } \" ]";
590- }
591- else
592- {
593- int index = siblings . IndexOf ( current ) + 1 ;
594- step += $ "[{ index } ]";
599+ // Attempt to use a unique attribute
600+ var uniqueAttr = siblings
601+ . SelectMany ( e => e . Attributes ( ) )
602+ . GroupBy ( a => a . Name . LocalName )
603+ . Where ( g => g . Count ( ) == siblings . Count ( ) )
604+ . Select ( g => g . Key )
605+ . FirstOrDefault ( ) ;
606+
607+ if ( uniqueAttr != null && current . Attribute ( uniqueAttr ) != null )
608+ {
609+ string value = current . Attribute ( uniqueAttr ) ! . Value . Replace ( "\" " , """ ) ;
610+ step += $ "[@{ uniqueAttr } =\" { value } \" ]";
611+ }
612+ else
613+ {
614+ // TODO: add sibling as unique additional id instead of index
615+ int index = siblings . IndexOf ( current ) + 1 ;
616+ step += $ "[{ index } ]";
617+ }
595618 }
596619 }
597620 path . Insert ( 0 , "/" + step ) ;
598621 current = current . Parent ! ;
599622 }
600623
624+
601625 if ( path . Length == 0 )
602626 path . Append ( "/" + root . Name . LocalName ) ;
603-
604- // If depth > 2, prefer using '//' with attributes
605- if ( path . ToString ( ) . Count ( c => c == '/' ) > 2 )
606- {
607- foreach ( var attr in element . Attributes ( ) )
608- {
609- string attrValue = attr . Value . Replace ( "\" " , """ ) ;
610- string xpath = $ "//{ element . Name . LocalName } [@{ attr . Name . LocalName } =\" { attrValue } \" ]";
611- var matches = root . XPathSelectElements ( xpath ) ;
612- if ( matches . Count ( ) == 1 )
613- return xpath ;
614- }
615- }
616-
617627 return path . ToString ( ) ;
618628 }
619629
0 commit comments