2

I have an .xml file that shares an attribute between two different elements. I am trying to multiply the attributes inside one elements with one variable, and multiply the attributes in the other element with a different variable.

        <acquirecosts>
          <item>
            <key>COST_SHOP_DEFAULT</key>
            <quantity value="1"/>
            <costtype>COST_TYPE_PRICE</costtype>
            <items>
              <item>
                <item>CURRENCY_CASH</item>
                <quantity value="6000"/>
              </item>
            </items>
            <unlocks/>
          </item>
        </acquirecosts>
        <sellprices>
          <item>
            <key>SELL_SHOP_DEFAULT</key>
            <quantity value="1"/>
            <costtype>COST_TYPE_PRICE</costtype>
            <items>
              <item>
                <item>CURRENCY_CASH</item>
                <quantity value="6000"/>
              </item>
            </items>
            <unlocks/>
          </item>
        </sellprices>

The "CURRENCY_CASH" quantity value inside <./acquirecosts> is being multiplied by 2, and the "CURRENCY_CASH" quantity value inside <./sellprices> is being multiplied by 0.5.

using System;
using System.Xml;
using System.Xml.XPath;

XmlDocument doc = new XmlDocument();
doc.Load(@"C:\Users\Darkye\Desktop\shopprices.xml");

var buyModifier = 2;
var sellModifier = 0.5;

var caItNodesBuy = caNode.XPathSelectElement("./acquirecosts").Elements();

foreach (var caItNodeBuy in caItNodesBuy)
{
    var caItNodeItems = caItNodeBuy.XPathSelectElement("./items").Elements();
    foreach (var item in caItNodeItems)
    {
        var caItNodeItemKey = item.Element("item").Value;
        if (caItNodeItemKey != "CURRENCY_CASH") continue;
        var caItNodeItemValue = (int)Math.Floor((double)int.Parse(item.Element("quantity").Attribute("value").Value) * buyModifier);
        item.Element("quantity").SetAttributeValue("value", caItNodeItemValue);
    }
    caItNodeBuy.XPathSelectElement("./items").ReplaceNodes(caItNodeItems);
}

caNode.XPathSelectElement("./acquirecosts").ReplaceNodes(caItNodesBuy);

var caItNodesSell = caNode.XPathSelectElement("./sellprices").Elements();

foreach (var caItNodeSell in caItNodesSell)
{
    var caItNodeItems = caItNodeSell.XPathSelectElement("./items").Elements();
    foreach (var item in caItNodeItems)
    {
        var caItNodeItemKey = item.Element("item").Value;
        if (caItNodeItemKey != "CURRENCY_CASH") continue;
        var caItNodeItemValue = (int)Math.Floor((double)int.Parse(item.Element("quantity").Attribute("value").Value) * sellModifier);
        item.Element("quantity").SetAttributeValue("value", caItNodeItemValue);
    }
    caItNodeSell.XPathSelectElement("./items").ReplaceNodes(caItNodeItems);
}

caNode.XPathSelectElement("./sellprices").ReplaceNodes(caItNodesSell);

But I am struggling to figure out what and where to introduce "caNode" as. I'm assuming it's a variable, but I'm lost beyond that. When changing caNode to "doc" it just introduces errors on XPathSelectElement. Unless there's an easier way of applying these edits inside specific elements, I'm not sure what else to try.

Darkye
  • 23
  • 3
  • 1
    You can continue with this approach, however you should likely just deserializse this, and work with it in memory (if possible) – TheGeneral Jul 12 '21 at 00:14
  • I'd prefer to stick with this. Trying to keep the xml as is. Originally I was going through manually, but there are so many varying values, and the amount of values is in the thousands, so it's very time consuming. Using System; to just multiply all the values for me and being able to save the xml as is, is the most ideal in this scenario – Darkye Jul 12 '21 at 00:31
  • (1) Your XML is not well-formed. It is missing a root element. (2) It is much better to use XSLT transformation for such tasks. – Yitzhak Khabinsky Jul 12 '21 at 01:10
  • This is just a portion of the xml file. The xml itself is very large. I just wanted to provide an example of and to better explain how I'm trying to automate the multiplication of the values under the two different elements – Darkye Jul 12 '21 at 01:24

2 Answers2

0

Please try the following solution.

It is using so called Identity Transform pattern.

It will modify <quantity> element @value attribute value based on the required logic without touching anything else.

Input XML

<root>
    <acquirecosts>
        <item>
            <key>COST_SHOP_DEFAULT</key>
            <quantity value="1"/>
            <costtype>COST_TYPE_PRICE</costtype>
            <items>
                <item>
                    <item>CURRENCY_CASH</item>
                    <quantity value="6000"/>
                </item>
            </items>
            <unlocks/>
        </item>
    </acquirecosts>
    <sellprices>
        <item>
            <key>SELL_SHOP_DEFAULT</key>
            <quantity value="1"/>
            <costtype>COST_TYPE_PRICE</costtype>
            <items>
                <item>
                    <item>CURRENCY_CASH</item>
                    <quantity value="6000"/>
                </item>
            </items>
            <unlocks/>
        </item>
    </sellprices>
</root>

XSLT

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" encoding="utf-8" indent="yes" omit-xml-declaration="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="quantity">
        <xsl:choose>
            <xsl:when test="preceding-sibling::*='CURRENCY_CASH'">
                <xsl:copy>
                    <xsl:attribute name="value">
                        <xsl:if test="ancestor::*[local-name() = 'acquirecosts']">
                            <xsl:value-of select="@value * 2"/>
                        </xsl:if>
                        <xsl:if test="ancestor::*[local-name() = 'sellprices']">
                            <xsl:value-of select="@value * 0.5"/>
                        </xsl:if>
                    </xsl:attribute>
                </xsl:copy>
            </xsl:when>
            <xsl:otherwise>
                <xsl:copy-of select="."/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet>

Output XML

<root>
  <acquirecosts>
    <item>
      <key>COST_SHOP_DEFAULT</key>
      <quantity value="1" />
      <costtype>COST_TYPE_PRICE</costtype>
      <items>
        <item>
          <item>CURRENCY_CASH</item>
          <quantity value="12000" />
        </item>
      </items>
      <unlocks />
    </item>
  </acquirecosts>
  <sellprices>
    <item>
      <key>SELL_SHOP_DEFAULT</key>
      <quantity value="1" />
      <costtype>COST_TYPE_PRICE</costtype>
      <items>
        <item>
          <item>CURRENCY_CASH</item>
          <quantity value="3000" />
        </item>
      </items>
      <unlocks />
    </item>
  </sellprices>
</root>

c#, XSLT transformation

void Main()
{
   const string SOURCEXMLFILE = @"e:\Temp\input.xml";
   const string XSLTFILE = @"e:\Temp\process.xslt";
   const string OUTPUTXMLFILE = @"e:\temp\output.xml";

   try
   {
      XsltArgumentList xslArg = new XsltArgumentList();

      using (XmlReader src = XmlReader.Create(SOURCEXMLFILE))
      {
         XslCompiledTransform xslt = new XslCompiledTransform();
         xslt.Load(XSLTFILE, new XsltSettings(true, true), new XmlUrlResolver());

         XmlWriterSettings settings = xslt.OutputSettings.Clone();
         settings.IndentChars = "\t";
         // to remove BOM
         settings.Encoding = new UTF8Encoding(false);

         using (XmlWriter result = XmlWriter.Create(OUTPUTXMLFILE, settings))
         {
            xslt.Transform(src, xslArg, result, new XmlUrlResolver());
            result.Close();
         }
      }
      Console.WriteLine("File '{0}' has been generated.", OUTPUTXMLFILE);
   }
   catch (Exception ex)
   {
      Console.WriteLine(ex.Message);
   }
}
Yitzhak Khabinsky
  • 18,471
  • 2
  • 15
  • 21
  • Is there a benefit to transforming it, before placing it back in xml format? Curious what application this has opposed to just editing the xml directly – Darkye Jul 12 '21 at 17:45
  • @Darkye, in this particular case, XSLT transforms input XML into output XML based on a required logic. Because we are using the **Identity Transform** pattern, the XSLT is not affecting the rest of the XML. It stays the same. XSLT is more powerful than direct XML manipulation via c# code. – Yitzhak Khabinsky Jul 12 '21 at 18:21
  • Well, for whatever reason I'm having the same issue with this. After building/publishing the program, it does nothing. Thought maybe it's because the .xml is too large, but even using your example one, still nothing when running the .exe. Console opens and closes immediately, no output.xml. Nothing. Really not sure what's wrong with how I'm compiling – Darkye Jul 12 '21 at 20:36
  • @Darkye, please contact me on LinkedIn. – Yitzhak Khabinsky Jul 12 '21 at 20:57
0

Use Xml Linq and do two passes

using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;

namespace ConsoleApplication1
{
    class Program
    {
        const string FILENAME = @"c:\temp\test.xml";
        static void Main(string[] args)
        {
            XDocument doc = XDocument.Load(FILENAME);

            XElement acquiredcosts = doc.Descendants("acquirecosts").FirstOrDefault();
            List<XElement> currentCash = acquiredcosts.Descendants("item").Where(x => (string)x.Element("item") == "CURRENCY_CASH").ToList();

            foreach (XElement c in currentCash)
            {
                XElement quantity = c.Element("quantity");
                quantity.SetAttributeValue("value", 2 * (int)quantity.Attribute("value"));
            }

            XElement sellPrices = doc.Descendants("sellprices").FirstOrDefault();
            currentCash = sellPrices.Descendants("item").Where(x => (string)x.Element("item") == "CURRENCY_CASH").ToList();

            foreach (XElement c in currentCash)
            {
                XElement quantity = c.Element("quantity");
                quantity.SetAttributeValue("value", .5 * (int)quantity.Attribute("value"));
            }
        }
    }
}
jdweng
  • 33,250
  • 2
  • 15
  • 20
  • Debug runs fine, and the script is compiled, but for whatever reason running the .exe does nothing – Darkye Jul 12 '21 at 07:05
  • Check the dates of exe in the debug and release folders. Make sure both got recompiled to latest version of source code. – jdweng Jul 12 '21 at 08:49
  • Yep, even started a new project and everything. Both are the most recent versions – Darkye Jul 12 '21 at 09:11
  • Never seen case like this. It is not the code in the project. Are there more than one project? Usually case like this there are more than one project and the one of the projects is running older version of source code. – jdweng Jul 12 '21 at 09:20
  • No, not sure what the issue is. I've even deleted all files and anything relating to other projects. Completely from scratch. I've run debug, release, publish and none of the produced .exe files do anything – Darkye Jul 12 '21 at 09:36
  • Did you delete any comments. I thought you said it worked in debug. I assumed that meant inside VS. Only the exe is not running. Does exe run on build machine? Did you publish after with to my Xml Linq Code (xdocument)? – jdweng Jul 12 '21 at 12:00
  • Nothing has been deleted. It’s in the .cs character for character. Works in debug as in no errors, VS says the build is successful. Same with running a release. I don’t think I tried on build machine. I have tried publishing it and running that .exe, no results. Not sure if it’s because the directory is C:\Users\Darkye\Desktop\test.xml, or something wrong with my output compile settings, or the template I’m using. But from what I can tell everything should be good. – Darkye Jul 12 '21 at 13:34
  • So I realized the issue was there was no doc.Save(); call after the file being loaded into memory, and edited. But only the first attribute that falls inside of a is modified. No others after that are – Darkye Jul 12 '21 at 21:31