2
\$\begingroup\$

This was a coding challenge I had done recently. I was given a text file and in it thee was couple of XML tags. The challenge was to get all the XML tags and the information and save it to another file.

The string I am given:

"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin nec arcu non nisl dapibus scelerisque. Phasellus sagittis ligula vel convallis porttitor. Maecenas sed velit arcu. Proin efficitur ipsum vitae augue semper, bibendum sollicitudin neque egestas".

<OpeningTag><AnotherOpeningTag>Information</AnotherClosingTag></ClosingTag>

"Etiam venenatis feugiat erat, in egestas tellus lacinia eu. Phasellus volutpat lectus nec tristique volutpat. Sed tempus sollicitudin mauris eu blandit.<OpeningTag><AnotherOpeningTag>Information</AnotherClosingTag></ClosingTag> Vestibulum consectetur euismod dui, sit amet blandit ex ultrices ac".

This is the code I came up with:

StringReader reader = new StringReader(text);
while ((text = reader.ReadLine()) != null)
{
if (text.StartsWith("<") && text.EndsWith(">"))
{
    txtXml += text + "\n";

}
else
{
    string regexPattern = @"(<.*>)(.*)(<\/.*>)";
    Regex regex = new Regex(regexPattern, RegexOptions.Singleline);

    MatchCollection collection = regex.Matches(text);

    var list = collection.Cast<Match>().Select(match => match.Value).ToList();

    for (int i = 0; i < list.Count; i++)
    {
        txtXml += list.ElementAt(i);
    }
}
}

This code works and I am getting all the tags and the information and stuff. I just wanted to know if I could achieve this same task in smaller code or code that executes faster than the current method.

\$\endgroup\$
  • 4
    \$\begingroup\$ The problem is underspecified. Do the XML fragments you are looking for always occupy a full line on their own? Are they always well-formed? What are you supposed to do it you find things that resemble XML but aren't well-formed, e.g. because the start and end tags don't match (as in your example)? Can CDATA sections and comments appear? You need to worry about whether the code does what it is required before you worry about performance or code size. \$\endgroup\$ – Michael Kay Sep 14 '18 at 23:21
1
\$\begingroup\$

There are some good points raised in comments about the requirements, and you really need to think about them in order to write code that solves the problem. I can get you started with some feedback though.

Organize your code into a function, so that it's clear what the inputs and outputs are.

StringReader reader = new StringReader(text);
while ((text = reader.ReadLine()

This is a really odd reuse of the variable text. Declare another string, maybe called line. But also reconsider if a line-centric approach is even appropriate. XML is pretty free about where whitespace (including newlines) can appear - what if there is a break between the tag name and some attributes? How will this affect your solution?

For the Regex @"(<.*>)(.*)(<\/.*>)", note that all of these quantifiers are greedy, which may result in you capturing more than you want. Of course, it may turn out with some clarified specs that a regex isn't even the appropriate tool to use here.

\$\endgroup\$
1
\$\begingroup\$

I have the following remarks:

I hope that this example was not a part of the challenge:

<OpeningTag><AnotherOpeningTag>Information</AnotherClosingTag></ClosingTag>

because it is actually not xml and should not be caught by your algorithm. A proper xml element has the same text in the start and end tags:

<OuterTag><InnerTag>Information</InnerTag></OuterTag>

Collecting results into one string is not very useful, because you then have to split that string when/if you want to use the result. So instead of:

txtXml += text + "\n";

... it would be better to collect each xml item in a collection of some sort:

List<string> result = new List<string>();

...
result.Add(text);

This condition:

if (text.StartsWith("<") && text.EndsWith(">")) { ... }

... will not only find:

"<OuterTag><InnerTag>Information</InnerTag></OuterTag>"

but also:

"<OuterTag><InnerTag>Information</InnerTag></OuterTag>XXXX<OuterTag><InnerTag>Information</InnerTag></OuterTag>"

and that IMO should not be the case, because "XXXX" is not part of any tag. The same applies to your Regex pattern.


None of your ways to find xml verifies that it actually is well formed xml, but that maybe was not part of the challenge?


You could use the .NET namespace System.Xml to do the things like:

IEnumerable<string> ExtractXml(string text)
{
  string candidate = $"<doc>{text}</doc>";
  XmlDocument xDoc = new XmlDocument();
  xDoc.LoadXml(candidate);
  return xDoc.FirstChild.Cast<XmlNode>().Where(n => n is XmlElement).Select(e => e.OuterXml);
}

IEnumerable<string> ExtractXml(IEnumerable<string> data)
{
  return data.SelectMany(line => ExtractXml(line));
}

Here I wrap the original text in an outer xml-element (<doc></doc>) which causes text outside xml-tags to be interpreted as XmlText elements that should be filtered out. By using the xml-library you can verify the xml at the same time as extracting it, because the xml-parser will throw on invalid format.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.