views:

4413

answers:

4

I have a quite big XML output from an application. I need to process it with my program and then feed it back to the original program. There are pieces in this XML which needs to be filled out our replaced. The interesting part looks like this:

<sys:customtag sys:sid="1" sys:type="Processtart" />
    <sys:tag>value</sys:tag>
    here are some other tags
    <sys:tag>value</sys.tag>
<sys:customtag sys:sid="1" sys:type="Procesend" />

and the document contains several pieces like this.

I need to get all XML pieces inside these tags to be able to make modifications on it. I wrote a regular expression to get those pieces but it does not work:

XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(@"output.xml");
Regex regExp = new Regex(@"<sys:customtag(.*?)Processtart(.*?)/>(.*?)<sys:customtag (.*?)Procesend(.*?)/>", RegexOptions.Multiline & RegexOptions.IgnorePatternWhitespace & RegexOptions.CultureInvariant);
MatchCollection matches = regExp.Matches(xmlDoc.InnerXml);

If I leave the whole stuff in one line and call this regexp without the multiline option, it does find every occurences. By leaving the file as it is and set the multiline option, it does not work. What is the problem, what should I change? Or is there any easier way to get the XML parts between these tags without regexp?

+7  A: 

i believe the option to use is RegexOptions.Singleline instead of RegexOptions.Multiline (src). allowing (.) to match newlines should work in your case.

...the mode where the dot also matches newlines is called "single-line mode". This is a bit unfortunate, because it is easy to mix up this term with "multi-line mode". Multi-line mode only affects anchors, and single-line mode only affects the dot ... When using the regex classes of the .NET framework, you activate this mode by specifying RegexOptions.Singleline, such as in Regex.Match("string", "regex", RegexOptions.Singleline).

Owen
That's it, thank you. I also meant multiline = multi line mode.
Biri
+3  A: 

RegExp is a poor tool for xml... could you not juts load it into an XDocument / XmlDocument and use xpath? If you clarify the modifications you want to make, I expect we can fill in the blanks... namespaces are probably the main thing to make it complex in this case, so we just need to use an XmlNamespaceManager.

Here's an example that is, granted, more complex than just a regex - however, I would expect it to cope a lot better with the nuances of xml:

    string xml = @"<foo xmlns:sys=""foobar""><bar/><bar><sys:customtag sys:sid=""1"" sys:type=""Processtart"" />
<sys:tag>value</sys:tag>
here are some other tags
<sys:tag>value</sys:tag>
<sys:customtag sys:sid=""1"" sys:type=""Procesend"" /></bar><bar/></foo>";

    XmlDocument doc = new XmlDocument();
    doc.LoadXml(xml);
    XmlNamespaceManager mgr = new XmlNamespaceManager(new NameTable());
    mgr.AddNamespace("sys", "foobar");
    var matches = doc.SelectNodes("//sys:customtag[@sys:type='Processtart']", mgr);
    foreach (XmlElement start in matches)
    {
        XmlElement end = (XmlElement) start.SelectSingleNode("following-sibling::sys:customtag[@sys:type='Procesend'][1]",mgr);
        XmlNode node = start.NextSibling;
        while (node != null && node != end)
        {
            Console.WriteLine(node.OuterXml);

            node = node.NextSibling;
        }
    }
Marc Gravell
I have looked up the XPath options but I haven't found anything which can give me back the XML content between to tags, which are not related XML-wise (I mean they are not start-close tags of each other from the point of XML). Maybe you have an idea?
Biri
Well, xml is intended to be used as a tree... a simple option would be to just use <sys:customtag ...>...</sys:customtag> - but I'll have a quick look...
Marc Gravell
Yes, I can handle that, but unfortunately the XML is coming from an application which I cannot change, and I have to give it back to the same application in this format. I cannot change the XML tags inside.
Biri
I'll update with an example, but personally I might be tempted to re-format the xml with xslt first...
Marc Gravell
This is also a nice solution, thank you. I'm going with regexp at the moment, but I will consider you approach for a second round with my program.
Biri
+1  A: 

The regex char "." never matches a newline, even with MultiLine option is set. instead, you should use [\s\S] or other combination with matches anything.

The MultiLine option only modifies the behaviour of ^ (begin-of-line instead fo begin-of-string) and $ (end-of-line instead of end-of-string)

BTW: Indeed, regex is not the right way to scan an HTML...

A: 

If you're still having problems with this, it may be because you are using AND with your RegexOptions instead of OR.

This code is wrong and will pass zero as the second parameter to the constructor:

Regex regExp = new Regex(@"<sys:customtag(.*?)Processtart(.*?)/>(.*?)<sys:customtag (.*?)Procesend(.*?)/>",
RegexOptions.Multiline & RegexOptions.IgnorePatternWhitespace & RegexOptions.CultureInvariant);

This code is correct (as far as using multiple RegexOptions flags):

Regex regExp = new Regex(@"<sys:customtag(.*?)Processtart(.*?)/>(.*?)<sys:customtag (.*?)Procesend(.*?)/>",
RegexOptions.Multiline | RegexOptions.IgnorePatternWhitespace | RegexOptions.CultureInvariant);
Charles