22

I have a string which contains XML, I just want to parse it into Xelement, but it has an ampersand. I still have a problem parseing it with HtmlDecode. Any suggestions?

string test = " <MyXML><SubXML><XmlEntry Element="test" value="wow&" /></SubXML></MyXML>"; 

XElement.Parse(HttpUtility.HtmlDecode(test));

I also added these methods to replace those characters, but I am still getting XMLException.

string encodedXml = test.Replace("&", "&amp;").Replace("<", "&lt;").Replace(">", "&gt;").Replace("\"", "&quot;").Replace("'", "&apos;");
XElement myXML = XElement.Parse(encodedXml);

t or Even tried it with this:

string newContent=  SecurityElement.Escape(test);
XElement myXML = XElement.Parse(newContent);
sao
  • 1,835
  • 6
  • 21
  • 40
paradisonoir
  • 2,892
  • 9
  • 30
  • 41

9 Answers9

27

Ideally the XML is escaped properly prior to your code consuming it. If this is beyond your control you could write a regex. Do not use the String.Replace method unless you're absolutely sure the values do not contain other escaped items.

For example, "wow&amp;".Replace("&", "&amp;") results in wow&amp;amp; which is clearly undesirable.

Regex.Replace can give you more control to avoid this scenario, and can be written to only match "&" symbols that are not part of other characters, such as &lt;, something like:

string result = Regex.Replace(test, "&(?!(amp|apos|quot|lt|gt);)", "&amp;");

The above works, but admittedly it doesn't cover the variety of other characters that start with an ampersand, such as &nbsp; and the list can grow.

A more flexible approach would be to decode the content of the value attribute, then re-encode it. If you have value="&wow&amp;" the decode process would return "&wow&" then re-encoding it would return "&amp;wow&amp;", which is desirable. To pull this off you could use this:

string result = Regex.Replace(test, @"value=\""(.*?)\""", m => "value=\"" +
    HttpUtility.HtmlEncode(HttpUtility.HtmlDecode(m.Groups[1].Value)) +
    "\"");
var doc = XElement.Parse(result);

Bear in mind that the above regex only targets the contents of the value attribute. If there are other areas in the XML structure that suffer from the same issue then it can be tweaked to match them and replace their content in a similar fashion.


EDIT: updated solution that should handle content between tags as well as anything between double quotes. Be sure to test this thoroughly. Attempting to manipulate XML/HTML tags with regex is not favorable as it can be error prone and over-complicated. Your case is somewhat special since you need to sanitize it first in order to make use of it.
string pattern = "(?<start>>)(?<content>.+?(?<!>))(?<end><)|(?<start>\")(?<content>.+?)(?<end>\")";
string result = Regex.Replace(test, pattern, m =>
            m.Groups["start"].Value +
            HttpUtility.HtmlEncode(HttpUtility.HtmlDecode(m.Groups["content"].Value)) +
            m.Groups["end"].Value);
var doc = XElement.Parse(result);
Ahmad Mageed
  • 94,561
  • 19
  • 163
  • 174
  • Your solution is perfect, but is it possible to use Regex for XML values as well. Cause as you said this only works for attributes. For example in this case: this & that – paradisonoir Sep 25 '09 at 14:24
  • @paradisonoir: yep, see my edit. As I said, make sure you test it thoroughly. – Ahmad Mageed Sep 25 '09 at 15:13
  • This approach seems to assume "one element at a time". Is that correct? I have a similar problem to the OP where I'm trying to clean an XML file prior to loading it, but my line has multiple nodes and the results are not what is expected. Is there a way to apply this pattern to a line with multiple nodes hello&world? – Bennett Dill Jan 05 '10 at 14:27
  • @Ben: try this pattern with the snippet that uses named groups above: `string pattern = "(?<=>)(?[^>]+)(?=<)|(?\")(?.+?)(?\")";` – Ahmad Mageed Jan 05 '10 at 17:44
  • Minor point, but   isn't an XML entity, it's an (X)HTML one; strictly speaking it's not valid XML anyway. As far as I can remember there are only 5 entities in XML: amp, lt, gt, apos and quot. However, you would need to check for ; entities. – Flynn1179 Jun 25 '10 at 08:45
  • 2
    ampersand could also be followed by number codes. ' can also be represented by ' or ' or ' use: string result = Regex.Replace(test, @"&(?!(quot|amp|apos|lt|gt|#x?\d{2,3});)", "&"); – o17t H1H' S'k Feb 28 '12 at 06:04
  • This solution causes me different encoding problems with an é getting turned into an é I think HtmlEncode is now encoding too much. – Richard Garside Dec 06 '12 at 15:18
  • Was a quick lifesaver. Thanks amigo – Jesse Johnson Sep 21 '20 at 00:13
14

Your string doesn't contain valid XML, that's the issue. You need to change your string to:

<MyXML><SubXML><XmlEntry Element="test" value="wow&amp;" /></SubXML></MyXML>"
Justin Niessner
  • 242,243
  • 40
  • 408
  • 536
3

HtmlEncode will not do the trick, it will probably create even more ampersands (for instance, a ' might become ", which is an Xml entity reference, which are the following:

&amp;   & 
&apos;  ' 
&quot;  " 
&lt;    < 
&gt;    > 

But it might you get things like &nbsp, which is fine in html, but not in Xml. Therefore, like everybody else said, correct the xml first by making sure any character that is NOT PART OF THE ACTUAL MARKUP OF YOUR XML (that is to say, anything INSIDE your xml as a variable or text) and that occurs in the entity reference list is translated to their corresponding entity (so < would become <). If the text containing the illegal character is text inside an xml node, you could take the easy way and surround the text with a CDATA element, this won't work for attributes though.

Colin
  • 10,630
  • 28
  • 36
  • 1
    but how do you suggest to do that? – paradisonoir Sep 24 '09 at 20:35
  • Well, I would suggest doing BEFORE you create the Xml file, assuming you are the one doing the creating of course. If you are not in control of the creation of the xml file (because for instance it is downloaded from somewhere), I suggest you contact the person responsible and have him sanitize the xml before sending it to you. – Colin Sep 24 '09 at 21:10
  • If you are in control of the creation of the xml file, use a RegEx (sorry, I suck at RegEx can't give example) or just a chained replacec like so: string.Replace("&", "&").Replace("'", """).etc.etc. – Colin Sep 24 '09 at 21:13
2

Filip's answer is on the right track, but you can hijack the System.Xml.XmlDocument class to do this for you without an entire new utility function.

XmlDocument doc = new XmlDocument();
string xmlEscapedString = (doc.CreateTextNode("Unescaped '&' containing string that would have broken your xml")).OuterXml;
TheAtomicOption
  • 1,456
  • 1
  • 12
  • 21
1

The ampersant makes the XML invalid. This cannot be fixed by a stylesheet so you need to write code with some other tool or code in VB/C#/PHP/Delphi/Lisp/Etc. to remove it or to translate it to &amp;.

Wim ten Brink
  • 25,901
  • 20
  • 83
  • 149
1

This is the simplest and best approach. Works with all characters and allows to parse XML for any web service call i.e. SharePoint ASMX.

public string XmlEscape(string unescaped)
        {
            XmlDocument doc = new XmlDocument();
            var node = doc.CreateElement("root");
            node.InnerText = unescaped;
            return node.InnerXml;
        }
0

If your string is not valid XML, it will not parse. If it contains an ampersand on its own, it's not valid XML. Contrary to HTML, XML is very strict.

Tommy Carlier
  • 7,951
  • 3
  • 26
  • 43
0

You should 'encode' rather than decode. But calling HttpUtility.HtmlEncode will not help you as it will encode your '<' and '>' symbols as well and your string will no longer be an XML.

I think that for this case the best solution would be to replace '&' with '& amp;' (with no space)

AlexS
  • 2,388
  • 15
  • 15
0

Perhaps consider writing your own XMLDocumentScanner. That's what NekoHTML is doing to have the ability to ignore ampersands not used as entity references.

Wilfred Springer
  • 10,869
  • 4
  • 55
  • 69