Is it possible to select HTML comments using QueryPath? - html

I see this is possible using jQuery, but how can it be done in QueryPath?
Selecting HTML Comments with jQuery
If not, can anyone suggest an HTML parser that can select comments?

QueryPath comes with an extension called QPXML that has several add-on methods. One of these is comment().
To use it, simply include it in your script:
include 'QueryPath/QueryPath.php';
include 'QueryPath/Extensions/QPXML.php';
htmlqp($html, $selector)->comment();
This will retrieve the first comment attached to the presently selected node(s).
If you have a really sophisticated set of comments all within the same nodes, you can do something like this:
$nodes = $qp->get();
foreach ($nodes as $node) {
foreach ($node->childNodes as $child) {
if ($child->nodeType == XML_COMMENT_NODE) {
// $child is a comment.
print $child->textContent;
}
}
}
This is a little uglier, but it gives better access to cases where one element has a lot of comments in it.

To get ALL comments of HTML page via querypath :
function getAllComments($node) {
if ($node->hasChildNodes()) {
foreach ($node->childNodes as $child) {
$this->getAllComments($child);
if ($child->nodeType == XML_COMMENT_NODE) {
echo $child->textContent;
}
}
}
}
$html = $qp->get() ;
getAllComments($html[0]);

Related

Use conditional logic to call alternative headers in site layout page

I am trying to find a way to use alternative headers in Razor Web Pages without using two _SiteLayout pages, with each _SiteLayout rendering a different _header page.
I’m trying to achieve this - If the default.cshtml page is called use header-1, if any other page is called use header-2.
I have tried all sorts of different logic with no joy, including: IsCurrentPage, Request.QueryString, Request.Url; and CurrentPage.Name.
E.G.
#if ((Request.QueryString["Default"] == null))
{
#RenderPage("/shared/_header-1.cshtml")
}
else
{
#RenderPage("/shared/_header-2.chtml")
}
And
#{
var pageUrl = this.Request.Url;
}
#if (pageUrl = "http://mycompany/Default.cshtml/") {
#RenderPage("/shared/_header-1.cshtml");
}
else
{
#RenderPage("/shared/_header-2.cshtml");
}
Does anyone have a simple method to achieve this please?
Although, I spent a long time on this, not long after posting I found an answer thanks to: Erik Philips
Add to _SiteLayout:
#if (IsSectionDefined("customHeader"))
{
#RenderSection("customHeader")
}
else
{
#RenderPage("/shared/_header.cshtml")
}
Add to default page
#section customHeader{
This is custom header
}
The common header doesn't get called in the Default page, because customHeader is specified instead; whereas, all other pages use the normal header

Disable "red links" on MediaWiki

I want to make the "red links" (links for uncreated pages) on a MediaWiki site to become plain text, save for people logged. Perhaps also make that them don't appear at all or only appear on different situations. You can "hide" them a bit with CSS, but I prefer to actually don't feature them.
You could use the LinkBegin hook to abort the link creation for page that do not exist. Something like this:
$wgHooks['LinkBegin'][] = 'ExampleExtension::exampleExtensionLinkBegin';
class ExampleExtension {
public static function exampleExtensionLinkBegin( $dummy, $target, &$html, &$customAttribs, $query, &$options, &$ret ) {
if ( $target->exists() ) {
return true;
} else {
$ret = $html;
return false;
}
} //exampleExtensionLinkBegin
}
edit: If you are not familiar with MW extension development, I recommend that you start by reading http://www.mediawiki.org/wiki/Manual:Developing_extensions and http://www.mediawiki.org/wiki/Manual:Hooks
If you know just a little bit of PHP, you should be able to follow that without any problems.

DOMDocument issues: Escaping attributes and removing tags from javascript

I am not fan of DOMDocument because I believe it is not very good for real world usages. Yet in current project I need to replace all texts in a page (which I don't have access to source code) with other strings (some sort of translation); so I need to use it.
I tried doing this with DOMDocument and I didn't received the expected result. Here is the code I use:
function Translate_DoHTML($body, $replaceArray){
if ($replaceArray && is_array($replaceArray) && count($replaceArray) > 0){
$body2 = mb_convert_encoding($body, 'HTML-ENTITIES', "UTF-8");
$doc = new DOMDocument();
$doc->resolveExternals = false;
$doc->substituteEntities = false;
$doc->strictErrorChecking = false;
if (#$doc->loadHTML($body2)){
Translate_DoHTML_Process($doc, $replaceArray);
$body = $doc->saveHTML();
}
}
return $body;
}
function Translate_DoHTML_Process($node, $replaceRules){
if($node->hasChildNodes()) {
$nodes = array();
foreach ($node->childNodes as $childNode)
$nodes[] = $childNode;
foreach ($nodes as $childNode)
if ($childNode instanceof DOMText) {
if (trim($childNode->wholeText)){
$text = str_ireplace(array_keys($replaceRules), array_values($replaceRules), $childNode->wholeText);
$node->replaceChild(new DOMText($text),$childNode);
}
}else
Translate_DoHTML_Process($childNode, $replaceRules);
}
}
And here are the problems:
Escaping attributes: There are data-X attributes in file that become escaped. This is not a major problem but it would be great if I could disable this behavior.
Before DOM:
data-link-content=" <a class="submenuitem" href=&quot
After DOM:
data-link-content=' <a class="submenuitem" href="
Removing of closing tags in javascript:
This is actually the main problem for me here. I don't know for what reason in the world DOMDocument may see any need to remove these tags. But it do. As you can clearly see in below example it remove closing tags in java-script string. It also removed last part of script. It seems like DOMDocument parse the java-script inside. Maybe because there is no CDATA tag? But any way it is HTML and we don't need CDDATA in HTML. I thought CDATA is for xHTML. Also I have no way to add CDDATA here. So can I ask it to not parse script tags?
Before DOM:
<script type="text/javascript"> document.write('<video src="http://x.webm"><p>You will need to Install the latest Flash plugin to view this page properly.</p></video>'); </script>
After DOM:
<script type="text/javascript"> document.write('<video src="http://x.webm"><p>You will need to <a href="http://www.adobe.com/go/getflashplayer" target="_blank">Install the latest Flash plugin to view this page properly.</script>
If there is no way for me to prevent these things, is there any way that I can port this code to SimpleHTMLDOM?
Thanks you very much.
Try this , and replace line content ;
$body2 = mb_convert_encoding($body, 'HTML-ENTITIES', "UTF-8");
to ;
$body2 = convertor($body);
and insert in your code ;
function convertor($ToConvert)
{
$FromConvert = html_entity_decode($ToConvert,ENT_QUOTES,'ISO-8859-1');
$Convert = mb_convert_encoding($FromConvert, "ISO-8859-1", "UTF-8");
return ltrim($Convert);
}
But use the right encoding in the context.
Have a nice day.
Based on my search, reason of the second problem is actually what "Alex" told us in this question: DOM parser that allows HTML5-style </ in <script> tag
But based on their research there is no good parser out there capable of understanding today's HTML. Also, html5lib's last update was 2 years ago and it failed to work in real world situations based on my tests.
So I had only one way to solve the second problem. RegEx. Here is the code I use:
function Translate_DoHTML_GetScripts($body){
$res = array();
if (preg_match_all('/<script\b[^>]*>([\s\S]*?)<\/script>/m', $body, $matches) && is_array($matches) && isset($matches[0])){
foreach ($matches[0] as $key => $match)
$res["<!-- __SCRIPTBUGFIXER_PLACEHOLDER".$key."__ -->"] = $match;
$body = str_ireplace(array_values($res), array_keys($res), $body);
}
return array('Body' => $body, 'Scripts' => $res);
}
function Translate_DoHTML_SetScripts($body, $scripts){
return str_ireplace(array_keys($scripts), array_values($scripts), $body);
}
Using above two functions I will remove any script from HTML so I can use DomDocument to do my works. Then again at the end, I will add them back exactly where they were.
Yet I am not sure if regex is fast enough for this.
And don't tell me to not use RegEx for HTML. I know that HTML is not a regular language and so on; but if you read the problem your self, you will suggest the same approach.

JSON API to show Advanced Custom Fields - WordPress

I am developing a magazine WordPress site that will have a json feed for a Mobile App. I set the backend up using Advanced Custom Fields with a Repeater Field for Multiple Articles and Multiple Pages within each article. http://www.advancedcustomfields.com/add-ons/repeater-field/
I am using the JSON API but this does not include any of my custom fields. Is there currently a plugin that can do this?
#Myke: you helped me tremendously. Here's my humble addition:
add_filter('json_api_encode', 'json_api_encode_acf');
function json_api_encode_acf($response)
{
if (isset($response['posts'])) {
foreach ($response['posts'] as $post) {
json_api_add_acf($post); // Add specs to each post
}
}
else if (isset($response['post'])) {
json_api_add_acf($response['post']); // Add a specs property
}
return $response;
}
function json_api_add_acf(&$post)
{
$post->acf = get_fields($post->id);
}
Update for Wordpress 4.7
With the release of Wordpress 4.7 the REST functionality is no longer provided as a distinct plugin, rather its rolled in (no plugin required).
The previous filters don't appear to work. However the following snippet does (can be in your functions.php):
>= PHP 5.3
add_filter('rest_prepare_post', function($response) {
$response->data['acf'] = get_fields($response->data['id']);
return $response;
});
< PHP 5.3
add_filter('rest_prepare_post', 'append_acf');
function append_acf($response) {
$response->data['acf'] = get_fields($response->data['id']);
return $response;
};
Note the filter is a wild card filter, applied like
apply_filters("rest_prepare_$type", ...
so if you have multiple content types (custom), you will need to do:
add_filter('rest_prepare_multiple_choice', 'append_acf');
add_filter('rest_prepare_vocabularies', 'append_acf');
function append_acf($response) {
$response->data['acf'] = get_fields($response->data['id']);
return $response;
};
Note It appears that rest_prepare_x is called per record. So if you are pinging the index endpoint, it will be called multiple times (so you don't need to check if its posts or post)
Came here by searching with the same question. This isn't totally vetted yet but I think this is getting on the right path. Check it out.
I have one less nested level than you do so this might need altered a bit. But the JSON API plugin has a filter called json_api_encode. I have a repeater called specifications that looks like this.
http://d.pr/i/YMvv
In my functions file I have this.
add_filter('json_api_encode', 'my_encode_specs');
function my_encode_specs($response) {
if (isset($response['posts'])) {
foreach ($response['posts'] as $post) {
my_add_specs($post); // Add specs to each post
}
} else if (isset($response['post'])) {
my_add_specs($response['post']); // Add a specs property
}
return $response;
}
function my_add_specs(&$post) {
$post->specs = get_field('specifications', $post->id);
}
Which appends a custom value to the JSON API output. Notice the get_field function from ACF works perfectly here for bringing back the array of the repeater values.
Hope this helps!
There is now a small plugin which adds the filter for you.
https://github.com/PanManAms/WP-JSON-API-ACF
I'm not sure if you're still interested in a solution, but I was able to modify the json-api plugin models/post.php file to display the repeater data as an array. This is a modification of a modification made by http://wordpress-problem.com/marioario-on-plugin-json-api-fixed-get-all-custom-fields-the-right-way/
replace the set_custom_fields_value() function with the following:
function set_custom_fields_value() {
global $json_api;
if ($json_api->include_value('custom_fields') && $json_api->query->custom_fields) {
// Query string params for this query var
$params = trim($json_api->query->custom_fields);
// Get all custom fields if true|all|* is passed
if ($params === "*" || $params === "true" || $params === "all") {
$wp_custom_fields = get_post_custom($this->id);
$this->custom_fields = new stdClass();
// Loop through our custom fields and place on property
foreach($wp_custom_fields as $key => $val) {
if (get_field($key)) {
$this->custom_fields->$key = get_field($key);
} else if ($val) {
// Some fields are stored as serialized arrays.
// This method does not support multidimensionals...
// but didn't see anything wrong with this approach
$current_custom_field = #unserialize($wp_custom_fields[$key][0]);
if (is_array($current_custom_field)) {
// Loop through the unserialized array
foreach($current_custom_field as $sub_key => $sub_val) {
// Lets append these for correct JSON output
$this->custom_fields->$key->$sub_key = $sub_val;
}
} else {
// Break this value of this custom field out of its array
// and place it on the stack like usual
$this->custom_fields->$key = $wp_custom_fields[$key][0];
}
}
}
} else {
// Well this is the old way but with the unserialized array fix
$params = explode(',', $params);
$wp_custom_fields = get_post_custom($this->id);
$this->custom_fields = new stdClass();
foreach ($params as $key) {
if (isset($wp_custom_fields[$key]) && $wp_custom_fields[$key][0] ) {
$current_custom_field = #unserialize($wp_custom_fields[$key][0]);
if (is_array($current_custom_field)) {
foreach($current_custom_field as $sub_key => $sub_val) {
$this->custom_fields->$key->$sub_key = $sub_val;
}
} else {
$this->custom_fields->$key = $wp_custom_fields[$key][0];
}
}
}
}
} else {
unset($this->custom_fields);
}
}
Current version of ACF prints out a custom_fields object on a call to the JSON API, containing all the fields relative to the Post or Page. I edited #Myke version to add specific custom fields from the ACF Option page to each Post or Page. Unfortunately there is not get_fields() function for the whole Option Page so you'll have to edit it depending on your fields structure.
add_filter('json_api_encode', 'json_api_encode_acf');
function json_api_encode_acf($response) {
if (isset($response['posts'])) {
foreach ($response['posts'] as $post) {
json_api_add_acf($post); // Add specs to each post
}
}
else if (isset($response['post'])) {
json_api_add_acf($response['post']); // Add a specs property
}
else if (isset($response['page'])) {
json_api_add_acf($response['page']); // Add a specs to a page
}
return $response;
}
function json_api_add_acf(&$post) {
$post->custom_fields->NAME_OF_YOUR_CUSTOM_FIELD = get_field( 'NAME_OF_YOUR_CUSTOM_FIELD', 'option' );
}

MediaWiki Extension question / suggestion

Complete beginner here. I want to create a new tab on each page that has a custom action. When clicked, it takes you to a new page which has custom HTML on it along with the text or the original article.
So far I could create a new Tab and could give a custom action mycustomaction to it. I am pasting what I did so far here. Please let me know if I am using the correct hooks etc. and what is a better way to achieve this basic functionality.
So far with their docs I have done this:
#Hook for Tab
$wgHooks['SkinTemplateContentActions'][] = 'myTab';
#Callback
function myTab( $content_actions) {
global $wgTitle;
$content_actions['0'] = array(
'text' => 'my custom label',
'href' => $wgTitle->getFullURL( 'action=mycustomaction' ),
);
return true;
}
#new action hook
$wgHooks['UnknownAction'][] = 'mycustomaction';
#callback
function mycustomaction($action, $article) {
echo $action;
return true;
}
This gives me error:
No such action
The action specified by the URL is invalid. You might have mistyped the URL, or followed an incorrect link. This might also indicate a bug in the software used by yourplugin
What I was doing wrong:
$content_actions[‘0’] should simply be $content_actions[] (minor nitpick)
$content_actions is passed-by-reference, it should be function myTab( &$content_actions ) {}
mycustomaction() should do something along the lines of
if ( $action == ‘mycustomaction’ ) {
do stuff; return false;
}
else {
return true;
}
It should use $wgOut->addHTML() instead of echo
Thanks a lot everyone for your help!