I have to parse HTML and "HTML" from emails. I've already managed to create a function that cleans most of the errors such as improper nesting of elements.
I'm trying to determine how best to tackle the issue of HTML attributes that are missing values. We must parse everything ultimately as XML so well-formed HTML is a must as well.
The cleaning function starts off simple enough:
$xml = explode('<', $xml);
We quickly determine opening and closing tags of elements.
However once we get to attributes things get really messy really quickly:
Missing values.
People using single quotes instead of double quotes.
Attribute values may contain single quotes.
Here is an example of an HTML string we have to parse (a p element):
$s = 'p obnoxious nonprofessional style=\'wrong: lulz-immature\' dunno>Some paragraph text';
We do not care what those attributes are; our goal is simply to fix the XML so that it is well-formed as demonstrated by the following string:
$s = 'p obnoxious="true" nonprofessional="true" style="wrong: lulz-immature" dunno="true">Some paragraph text';
We're not interested in attribute="attribute" as that is just extra work (most email is frivolous) so we're simply interested in appending ="true" for each attribute missing a value just to prevent the XML parser on client browsers from failing over the trivialities of someone somewhere else not doing their job.
As I mentioned earlier we only need to fix the attributes which are missing values and we need to return a string. At this point all other issues of malformed XML have been addressed. I'm not sure where I should start as the topic is such a mess. So...
We're open to sending the entire XML string as a whole to be parsed and returned back as a string with some built in library. If this option presume that the XML is well-formed with a proper XML declaration (<?xml version="1.0" encoding="UTF-8"?>).
We're open to manually creating a function to address whatever we encounter though we're not interested in building a validator as much of the "HTML" we receive screams 1997.
We are working with the XML as a single string or an array (your pick); we are explicitly not dealing with files.
How do we with reasonable effort ensure that an XML string (in part or whole) is returned as a string with values for all attributes?
The DOM extension may solve your problem:
$doc = new DOMDocument('1.0');
$doc->loadHTML('<p obnoxious nonprofessional style=\'wrong: lulz-immature\' dunno>Some paragraph text');
echo $doc->saveXML();
The above code will result in the following output:
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body><p obnoxious="" nonprofessional="" style="wrong: lulz-immature" dunno="">Some paragraph text</p></body></html>
You may replace every ="" with ="true" if you want, but the output is already a valid XML.
I'm trying this:
<root>
text:
</root>
But parser says:
org.xml.sax.SAXParseException; lineNumber: 2; columnNumber: 12;
Character reference "" is an invalid XML character.
at org.apache.xerces.parsers.DOMParser.parse(Unknown Source)
at org.apache.xerces.jaxp.DocumentBuilderImpl.parse(Unknown Source)
at javax.xml.parsers.DocumentBuilder.parse(DocumentBuilder.java:121)
How to use it there?
You cannot ( in XML 1.0). In XML 1.1 there is a slightly larger range of characters you can use, and the characters are expressed differently, but even then, is 'restricted' (hex it is ) which, as far as I can tell, means that it is not valid XML, even though XML parsers should process it successfully. Note that the 'null' character () is never valid. Here's a Wiki article on these characters in XML
You can try forcing the XML document to XML 1.1 and see if your parser will process it successfully.... set the first line of the XML to:
<?xml version="1.1"?>
In fact, I did that, and it works:
<?xml version="1.1"?>
<root></root>
I am using a payment gateway to send xml via CURL. I am getting the following error when I use an XML Validator:
Errors in the XML document: The entity "auml" was referenced, but not
declared.
So I understand the problem lies with the ä, however I am unsure on how to fix this using PHP.
Here is the xml request I am passing:
<request type='payer-new' timestamp='XXXXXX'>
<merchantid>XXXXXXXXX</merchantid>
<orderid>XXXXXXXXX</orderid>
<payer type='Business' ref='XXXXXXXXXXXXX'>
<firstname>Xäxxxx</firstname>
<surname>xäxxxxxx</surname>
<address>
<line1>XXXXXXXXXXXXX</line1>
<line2>XXXXXXXXXXXXX</line2>
<city>XXXXXXXXXXXXXXXX</city>
<postcode>XXXXXXXXXXXX</postcode>
<country code='FI'>Finland</country>
</address>
<phonenumbers>
<home>XXXXXXXXXXXXXXXXXXXXXXXXX</home>
</phonenumbers>
<email>XXXXXXXXXXXXXXXXXXXXXXXXXXXXX</email>
</payer>
<sha1hash>XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</sha1hash>
</request>
I wrap htmlentities around all of the variables going into the request like so:
".htmlentities($_SESSION['W_CUSTOMER_FIRSTNAME'], ENT_QUOTES,
"UTF-8")."
Is there a way that will work with all kinds of characters / names / places etc that contain these characters?
Many thanks in advance
ä is an HTML entity code, not a generic XML one.
Generic XML only understands three named entities: &, > and <.
If you want to use any other named entities such as ä, those entities must be defined in the XML schema definition. Some standardised XML dialects have schemas which define named entities, but most do not, and if you don't have a schema, then you definitely won't be able to use any named entities.
So instead of using named entities in XML, it is generally better to use numeric entities. These take the form of Ӓ, where 1234 is the character code for the character you want. For a auml character, the code you need is ä. Note that these numeric entity codes can also work fine in HTML.
You can find a list of some of the more useful character codes here: http://www.econlib.org/library/asciicodes.html
Annoyingly, there isn't a standard PHP function that can produce these numeric XML entities. The htmlentities() and html_special_chars() functions are not suitable, as they produce named entities. So we have to write our own.
You'll need to use the ord() function to get the character code, but be aware of multi-byte characters. There is actually a reasonable attempt at an xmlentities() function in the comments on the manual page for htmlentities(), which you could try. I know other implementations exist, though.
The fundamental problem is you have an XML document that is using HTML entities to encode things. An XML Validator knows nothing about HTML-specific entities, and so will choke.
I would hope that there is an XSD (schema) for the XML; it should really be declared in the root tag with an xmlns declaration and possibly with a xsi:schemaLocation too. This XSD file would be the right place to xsd:import the html entities that would enable your validator to validate correctly. There should also be an <?xml vers... > tag as the first line.
That said, I suspect that the receiving application won't care what the validator says, and that your response file is probably just fine, assuming the receiver knows about html entities too.
If not, you need to decode the html entities into actual utf8 characters, but probably do so just on the text elements of the DOM (e.g. the content of <email> not the whole text). Doing this with php's html_entity_decode() would seem reasonable. If you do this you definitely need the <?xml> tag to include the file charset.
HTH
I wrap htmlentities around all of the variables going into the request like so: ...
There is your problem. You're creating the XML string "by hand". Not that it wouldn't be possible to do so, it's just easy to make mistakes by doing so. One hint could be the name of the function you use already, it starts with "html" which is not XML.
Anyway, before discussing in depth to which extend interpolating strings can cause troubles for creating XML and when such problems arise, it's much easier to use an XML library to create the XML.
An XML libary allows you to encode all data properly (so you won't see such errors) and with ease. In PHP there are normally three:
SimpleXML
DOM
XMLWriter
Take the one you can work best with.
Alternatively you can verify that the XML you created "by hand" is well-formed before you send it to the remote service, by use of one of the following XML libraries as they are also XML parsers:
SimpleXML
DOM
Q&A material on how to create an XML document with either of these already exists on this website - even with examples and comments on them - so I don't duplicate such content in my answer. Same for the XML validation.
Example of an XML preset (pattern) of a request of which some parameters are set. Here with SimpleXML:
$pattern = <<<REQUEST_PATTERN
<request type='payer-new' timestamp='XXXXXX'>
<merchantid>XXXXXXXXX</merchantid>
<orderid>XXXXXXXXX</orderid>
<payer type='Business' ref='XXXXXXXXXXXXX'>
<firstname></firstname>
<surname></surname>
<address>
<line1>XXXXXXXXXXXXX</line1>
<line2>XXXXXXXXXXXXX</line2>
<city>XXXXXXXXXXXXXXXX</city>
<postcode>XXXXXXXXXXXX</postcode>
<country code='FI'>Finland</country>
</address>
<phonenumbers>
<home>XXXXXXXXXXXXXXXXXXXXXXXXX</home>
</phonenumbers>
<email>XXXXXXXXXXXXXXXXXXXXXXXXXXXXX</email>
</payer>
<sha1hash>XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</sha1hash>
</request>
REQUEST_PATTERN;
$xml = simplexml_load_string($pattern);
$xml->payer->firstname = 'Äpfel';
$xml->payer->surname = 'Wachsen-Überirdisch';
# ...
// just an assumed way on how you would pass the XML string
// to the API via CURL (here as HTTP POST request body)
curl_setopt($handle, CURLOPT_POSTFIELDS, $xml->asXML());
The XML that would be passed to the remote service would always(*) be XML encoded in a proper way:
<?xml version="1.0"?>
<request type="payer-new" timestamp="XXXXXX">
<merchantid>XXXXXXXXX</merchantid>
<orderid>XXXXXXXXX</orderid>
<payer type="Business" ref="XXXXXXXXXXXXX">
<firstname>Äpfel</firstname>
<surname>Wachsen-Überirdisch</surname>
<address>
<line1>XXXXXXXXXXXXX</line1>
<line2>XXXXXXXXXXXXX</line2>
<city>XXXXXXXXXXXXXXXX</city>
<postcode>XXXXXXXXXXXX</postcode>
<country code="FI">Finland</country>
</address>
<phonenumbers>
<home>XXXXXXXXXXXXXXXXXXXXXXXXX</home>
</phonenumbers>
<email>XXXXXXXXXXXXXXXXXXXXXXXXXXXXX</email>
</payer>
<sha1hash>XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</sha1hash>
</request>
(*) there are some rare circumstances where this would not be the case, but they should not play any role for this example: The SimpleXML library requires properly UTF-8 encoded strings to work.
I created such function for XML strings safe replacing:
/**
* Safe symbols escaping for XML. It's very similar to htmlspecialschars for html + mysql.
* #param string $string
* #return string
*/
public static function xmlentities(string $string): string
{
return htmlspecialchars($string, ENT_XML1, 'UTF-8', true);
}
Usage:
$str = 'Dafür hörten "der Relativen Schwäche“-Entwicklung gegenüber den Wall Street';
$str = static::xmlentities($str);
echo '<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
<set>'.$str.'</set>
</urlset>';
Explanation:
ENT_XML1 set's the list of symbols accepted for XML documents
'UTF-8' sets the charset so ä and other German/national symbols becomes accepted as a symbol
true -- the last TRUE parameter makes converting safe sins it often happens that incoming string has already some htmlspecialchars precessed symbols (e.g. during the loading from DB or getting from the API)
Remember to put charset declaration to the document header
<?xml version="1.0" encoding="UTF-8"?>
or to set some other charset for htmlspecialchars
I have suffered same issue and i have resolves issue.
$str = 'Dafür hörten "der Relativen Schwäche“-Entwicklung gegenüber den Wall Street';
echo htmlspecialchars($str, ENT_XML1, 'UTF-8', true);
I hope,this is usefull and it's working
I'm trying to read an XML feed, I'm not sure the encoding is proper, but it's set to UTF-8 and when I try to parse it in PHP via SimpleXML, it errors on "BöðVar" (note the special "o" characters).
libxml_use_internal_errors(TRUE);
$XMLOutputXMLObj = simplexml_load_string($xml_string);
if($XMLOutputXMLObj !== FALSE)
{
//do stuff
}
This is all I get for an error:
Entity 'ouml' not defined
Entity 'eth' not defined
I tried using "mb_convert_encoding", in various ways, but that failed.
How can I resolve this issue for any character? IE WITHOUT manually replacing ö with &214; (with # of course)?
Even better... is there a way to make it so SimpleXML doesn't care what it is parsing, as long as the tags are intact?
Thanks
Have you tried to escape the XML data in the node using the <![CDATA[ and ]]> tags before and after the node's text/value? E.g.
<?xml version="1.0" encoding="UTF-8"?>
<fmsdata>
<result><![CDATA[Success !##$%^&*()]]></result>
</fmsdata>
I am creating XML files with PHP DOMDocument, and these XML files can not contain line breaks.
But when I use the method "saveXML()", the generated XML comes with a line break between the definition and the initial tag, like this:
<?xml version="1.0" encoding="UTF-8"?>
<NFe xmlns="http://www.portalfiscal.inf.br/nfe"><infNFe...
Can I correct this problem in DOMDocument? Or do I have to do it after I generate the XML?
I'd like to correct this problem to get a result like this:
<?xml version="1.0" encoding="UTF-8"?><NFe xmlns="...
By default, DOMDocument::$preserveWhiteSpace is true. Try setting it to false on the document in question, then calling saveXML again. This may have side effects should any whitespace inside the document actually matter. You should also make sure that DOMDocument::$formatOutput is false.
As said by Gordon, though, there is no logical reason whatsoever for the whitespace restriction. Though seriously, if you don't want any whitespace in there whatsoever, just make sure any CR/LF characters that you want to keep are entity-encoded and then $nonewlines = preg_replace("/[\r\n]/", '', $xml) to yank out the newlines that might remain after turning off Preserve and Format. But again, that's silly.