Skip to content

Commit b25013f

Browse files
committed
refactor: simplify GenerateXPath method and make one parent add element for the several added element
1 parent 2b2294f commit b25013f

File tree

1 file changed

+66
-56
lines changed

1 file changed

+66
-56
lines changed

XMLDiff/Program.cs

Lines changed: 66 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -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("\"", "&quot;");
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("\"", "&quot;");
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("\"", "&quot;");
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("\"", "&quot;");
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

Comments
 (0)