0

Given some input data:

<somexml>
    <User Name="MrFlibble">
        <Option Name="Pass">SomeSaltedPassword</Option>
        <Option Name="Salt">Salt</Option>
        <tag1></tag1>
        <Permissions>
            <Permission Dir="E:"></Permission>
        </Permissions>
    </User>
    <User Name="MrFlobble">
        <Option Name="Pass">SomeOtherSaltedPassword</Option>
        <Option Name="Salt">Salt</Option>
        <tag1></tag1>
        <Permissions>
            <Permission Dir="C:"></Permission>
        </Permissions>
    </User>
</somexml>

I'd like to replace the first user that doesn't have a C: permission in the user area (in this case MrFlibble) with Jon and SomeSaltedPassword with MyNewSaltedPassword using a .net framework regex to give the following result:

<somexml>
    <User Name="Jon">
        <Option Name="Pass">MyNewSaltedPassword</Option>
        <Option Name="Salt">Salt</Option>
        <tag1></tag1>
        <Permissions>
            <Permission Dir="E:"></Permission>
        </Permissions>
    </User>
    <User Name="MrFlobble">
        <Option Name="Pass">SomeOtherSaltedPassword</Option>
        <Option Name="Salt">Salt</Option>
        <tag1></tag1>
        <Permissions>
            <Permission Dir="C:"></Permission>
        </Permissions>
    </User>
</somexml>

I think something like this regex would capture the users and group the sections I Want to replace:

<User Name="(.*)">.*<Option Name="Pass">(.*)<\/Option>.*<Option Name="Salt">(.*)<\/Option>.*<\/User>

...but I'm struggling to see how I would substitute the three groups while maintaining the other text. The docs all seem to suggest replacing modifications of the original text rather than multiple specifically named groups with specific new text.

Is there a standard way to do this or am I barking up the wrong tree?

Jon Cage
  • 36,366
  • 38
  • 137
  • 215

3 Answers3

2

Do not under any circumstances try to parse XML with a regex unless you wish to invoke rite 666 Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn.

Use an XML parsing library see this page for some ways to do it.

JGNI
  • 3,933
  • 11
  • 21
  • Second answer down in that first link more closely describes my use case I _think_. This is some well-formed XML in a very closed environment, not scraping some possibly horribly formed XHTML from a random website. – Jon Cage Feb 01 '19 at 17:13
  • 2
    Took your advice and narrowly closed the gate before it had fully formed. Took -4 sanity in the process and woke up in Arkham Asylum for my trouble though... ...Dr Dobbs got me through it! – Jon Cage Feb 13 '19 at 09:47
1

This is quite difficult to do with regular expressions because you need a replacement by condition.

In the comment you wrote that it is well-formed xml. Therefore, I dare to offer a solution using xml parser.

Add reference to System.Xml.Linq library to the project.
Open the following namespaces

using namespace System;
using namespace System::IO;
using namespace System::Xml::Linq;

The code is very simple and concise

//auto xml = XElement::Parse(input); // input - string containing your xml
auto xml = XElement::Load(L"test.xml");

for each (auto user in xml->Elements(L"User"))
{
    if (user->Element(L"Permissions")->Element(L"Permission")->Attribute(L"Dir")->Value != L"C:")
    {
        user->Attribute(L"Name")->Value = L"Jon";

        for each(auto option in user->Elements(L"Option"))
        {
            if (option->Attribute(L"Name")->Value == L"Pass")
            {
                option->Value = L"MyNewSaltedPassword";
            }
        }
    }
}

Console::WriteLine(xml);
//xml->Save(L"result.xml");
Alexander Petrov
  • 13,457
  • 2
  • 20
  • 49
1

Option with regular expressions. The expression itself looks obscure, as a result it is difficult to maintain. Therefore, it is better to use the method with the xml parser.

using namespace System;
using namespace System::IO;
using namespace System::Text::RegularExpressions;

MatchEvaluator method:

String^ Evaluate(Match^ m)
{
    if (m->Groups[L"dir"]->Value != L"C:")
        return L"Jon" + m->Groups[L"mid1"] + L"MyNewSaltedPassword" + m->Groups[L"mid2"] + m->Groups[L"dir"];
    else
        return m->Groups[L"name"]->Value + m->Groups[L"mid1"] + m->Groups[L"pass"] + m->Groups[L"mid2"] + m->Groups[L"dir"];
}

Code:

auto input = File::ReadAllText(L"test.xml");

auto pattern = gcnew String(R"(
(?<= <User \s Name = " )
(?'name' .+? )
(?= "> )

(?'mid1' .+? )

(?<= <Option \s Name = "Pass"> )
(?'pass' .+? )
(?= </Option> )

(?'mid2' .+? )

(?<= <Permission \s Dir = " )
(?'dir' .+? )
(?= "> )
)");

auto options = RegexOptions::IgnorePatternWhitespace | RegexOptions::Singleline;

auto evaluator = gcnew MatchEvaluator(Evaluate);
auto result = Regex::Replace(input, pattern, evaluator, options);

Console::WriteLine(result);
Alexander Petrov
  • 13,457
  • 2
  • 20
  • 49