4/21/2012

Звездочки рейтинга в результатх поиска Google

Наверняка многие из вас замечали, что для некоторых результатов поиска Google показывает текущий рейтинг, который соответствует данному результату на Вашем сайте. Пример того, как выводится подобный результат в Google приведен ниже:


Согласитесь, вероятность того, что такая ссылка имеет больше шансов привлечь внимание пользователя, чем стандартные результаты поиска. Добиться того, чтобы Google отображал результаты подобным образом совсем насложно, используя специальную разметку, которую кроме Google понимают Microsoft, Yahoo!, и другие поисковые системы, которая применяется для результатов рейтингов и обзорово.

Существуют несколько вариантов подобной разметки. Я ограничусь писанием одного из них, про остальные можно почитать здесь: http://support.google.com/webmasters/bin/answer.py?hl=en&answer=172705. Итак, на кадой странице, которая может иметь рейтинг, обязательно должны присутствовать следующие элементы:

1. Название объекта, который оценивается
2. Текущий средний рейтинг
3. Количество голосов или обзоров, на которых строится рейтинг.

Для этого, содержимое страницы, которое содержит информацию о рейтинге, должно быть обравлмлено в тэг с аттрибутаит itemscope и i itemtype="http://data-vocabulary.org/Review-aggregate", например:


HTML тэгу, который обрамляет заголовок объкета надо добавить аттрибут itemprop="itemreviewed". Например:

Мойка "Автобаня"


Средний рейтинг для объекта можно задать в специальном мета-тэге:



Обратите внимание, что Google оперирует шкалой рейтингов от 0 до 5. Если Вы испольщуете другую шкалу, то задать диапазон шкалы можно таким образом:

 




И наконец, количество голосов, на которых базируется рейтинг задается таким образом:

1 отзыв

Для того, чтобы удоставриться, что Вы все сделали правильно, Вам необязательно дожидаться, пока Google проиндексирует страницу. Для проверки нужно ввести ссылку Вашу страницу здесь: http://www.google.com/webmasters/tools/richsnippets

Описанный выше подход успешно применен на сайте отзывов об организациях Киева Отзычик.

Playing video in .NET Windows Forms application

I have a Windows Forms application developed in C#.NET that I used to work on a few years ago which includes functionality that is supposed to display fragments from a video file on form. Everything was fine while the application was working in Windows XP: there are a number of ways to play the video in a .NET application that worked there.One of them, that I was using, is the .NET wrapper for the native windows avifil32 library, which used to play .AVI files just fine. But this approach stopped working in Windows 7. I have also tried Managed DirectX but sing the Managed DirectX was discontinued a while back, it did not work in Windows 7 either. And since I just had to fix the issue I did not feel like rewriting the application with WPF.

Fortunately I found the library called DirectShow.NET, which provides .NET wrappers for native DirectShow calls. There are a number of examples included with the library, so getting it to work was fairly simple. Also, to make my life a little easier I wrote a simple class that allows playing Video in a given Control on a Form (I used PictureBox for this), which I would like to discuss below.

The VideoPlayer class is displayed below. The constructor of the class takes a path to the file that needs to be played and the control where the video should be displayed. The class allows to play fragments from the video starting at a given second and ending at a given second (see method SetFragment, which must be called before the Play method if you just want to play the fragment and not the entire video). There is also a SetRate method that allows to set the speed with which the video will be played.

The use of the class if fairly simple:
1. Download and link to your project the DirectShow.NET library from DirectShow.NET.
2. Copy and paste the VideoPlayer.cs class below and include it into your project.
3. Create a PictureBox control on a form (or any other control that you see fit).
4. Instantiate a VideoPlayer class, providing a path to the file and a reference to a control you just added.
5. Optionally set the frame rate and the fragment start and end positions.
6. Call Play method to start the video and Pause method to stop it.
7. Use the Close method when the video is not needed.

So here comes the code of the VideoPlayer class (a lot which is simply copied and pasted from one of the examples included with DirectShow.NET):

using System;
using System.Collections.Generic;
using System.Text;
using DirectShowLib;
using System.Windows.Forms;
using System.Runtime.InteropServices;


namespace VideoUtils
{
    public class VideoPlayer
    {

        public enum PlayState
        {
            Stopped,
            Paused,
            Running,
            Init
        };

        private Control owner;
        private string FileName;

        private IGraphBuilder graphBuilder = null;
        private IMediaControl mediaControl = null;
        private IMediaEventEx mediaEventEx = null;
        private IVideoWindow videoWindow = null;
        private IBasicAudio basicAudio = null;
        private IBasicVideo basicVideo = null;
        private IMediaSeeking mediaSeeking = null;
        private IMediaPosition mediaPosition = null;
        private IVideoFrameStep frameStep = null;

        private int currentVolume = 0;
        private PlayState currentState = PlayState.Stopped;
        public PlayState state {
            get { return currentState; }
        }

        public VideoPlayer(string file, Control owner)
        {
            this.owner = owner;
            this.FileName = file;

            this.currentState = PlayState.Stopped;
            this.currentVolume = -10000; // muting the audio

            InitializePlayer();
        }

        private void InitializePlayer()
        {
            int hr = 0;

            if (FileName == string.Empty)
                return;

            this.graphBuilder = (IGraphBuilder)new FilterGraph();

            // Have the graph builder construct its the appropriate graph automatically
            hr = this.graphBuilder.RenderFile(FileName, null);
            DsError.ThrowExceptionForHR(hr);

            // QueryInterface for DirectShow interfaces
            this.mediaControl = (IMediaControl)this.graphBuilder;
            this.mediaEventEx = (IMediaEventEx)this.graphBuilder;
            this.mediaSeeking = (IMediaSeeking)this.graphBuilder;
            this.mediaPosition = (IMediaPosition)this.graphBuilder;

            // Query for video interfaces, which may not be relevant for audio files
            this.videoWindow = this.graphBuilder as IVideoWindow;
            this.basicVideo = this.graphBuilder as IBasicVideo;

            // Query for audio interfaces, which may not be relevant for video-only files
            this.basicAudio = this.graphBuilder as IBasicAudio;

            // Have the graph signal event via window callbacks for performance
            // hr = this.mediaEventEx.SetNotifyWindow(owner, WMGraphNotify, IntPtr.Zero);
            DsError.ThrowExceptionForHR(hr);

                // Setup the video window
            hr = this.videoWindow.put_Owner(owner.Handle);
            DsError.ThrowExceptionForHR(hr);

            hr = this.videoWindow.put_WindowStyle(WindowStyle.Child | WindowStyle.ClipSiblings | WindowStyle.ClipChildren);
            DsError.ThrowExceptionForHR(hr);

            GetFrameStepInterface();

            hr = this.videoWindow.SetWindowPosition(0, 0, owner.Width, owner.Height);

            hr = this.basicAudio.put_Volume(this.currentVolume);
        }

        public void Play()
        {
            int hr = 0;
            // Run the graph to play the media file
            hr = this.mediaControl.Run();
            DsError.ThrowExceptionForHR(hr);

            this.currentState = PlayState.Running;
        }

        //
        // Some video renderers support stepping media frame by frame with the
        // IVideoFrameStep interface.  See the interface documentation for more
        // details on frame stepping.
        //
        private bool GetFrameStepInterface()
        {
            int hr = 0;

            IVideoFrameStep frameStepTest = null;

            // Get the frame step interface, if supported
            frameStepTest = (IVideoFrameStep)this.graphBuilder;

            // Check if this decoder can step
            hr = frameStepTest.CanStep(0, null);
            if (hr == 0)
            {
                this.frameStep = frameStepTest;
                return true;
            }
            else
            {
                // BUG 1560263 found by husakm (thanks)...
                // Marshal.ReleaseComObject(frameStepTest);
                this.frameStep = null;
                return false;
            }
        }

        /*
         * Media Related methods
         */

        public void Pause()
        {
            if (this.mediaControl == null)
                return;

            // Toggle play/pause behavior
            if ((this.currentState == PlayState.Paused) || (this.currentState == PlayState.Stopped))
            {
                if (this.mediaControl.Run() >= 0)
                    this.currentState = PlayState.Running;
            }
            else
            {
                if (this.mediaControl.Pause() >= 0)
                    this.currentState = PlayState.Paused;
            }
        }

        public void SetFragment(long start_seconds, long stop_seconds)
        {
            // In Directx time is measured in 100 nanoseconds. 

            DsLong pos_start = new DsLong(start_seconds * 10000000);
            DsLong pos_stop = new DsLong(stop_seconds * 10000000);
            int hr = 0;
            // Seek to the position
            hr = this.mediaSeeking.SetPositions(pos_start, AMSeekingSeekingFlags.AbsolutePositioning, pos_stop, AMSeekingSeekingFlags.AbsolutePositioning);
        }

        public void CloseFile()
        {
            int hr = 0;

            // Stop media playback
            if (this.mediaControl != null)
                hr = this.mediaControl.Stop();

            // Clear global flags
            this.currentState = PlayState.Stopped;

            // Free DirectShow interfaces
            CloseInterfaces();

            // Clear file name to allow selection of new file with open dialog

            // No current media state
            this.currentState = PlayState.Init;

        }

        private void CloseInterfaces()
        {
            int hr = 0;

            try
            {
                lock (this)
                {
                    // Relinquish ownership (IMPORTANT!) after hiding video window
                    hr = this.videoWindow.put_Visible(OABool.False);
                    DsError.ThrowExceptionForHR(hr);
                    hr = this.videoWindow.put_Owner(IntPtr.Zero);
                    DsError.ThrowExceptionForHR(hr);

                    if (this.mediaEventEx != null)
                    {
                        hr = this.mediaEventEx.SetNotifyWindow(IntPtr.Zero, 0, IntPtr.Zero);
                        DsError.ThrowExceptionForHR(hr);
                    }

                    // Release and zero DirectShow interfaces
                    if (this.mediaEventEx != null)
                        this.mediaEventEx = null;
                    if (this.mediaSeeking != null)
                        this.mediaSeeking = null;
                    if (this.mediaPosition != null)
                        this.mediaPosition = null;
                    if (this.mediaControl != null)
                        this.mediaControl = null;
                    if (this.basicAudio != null)
                        this.basicAudio = null;
                    if (this.basicVideo != null)
                        this.basicVideo = null;
                    if (this.videoWindow != null)
                        this.videoWindow = null;
                    if (this.frameStep != null)
                        this.frameStep = null;
                    if (this.graphBuilder != null)
                        Marshal.ReleaseComObject(this.graphBuilder); this.graphBuilder = null;

                    GC.Collect();
                }
            }
            catch
            {
            }
        }

        public int SetRate(double rate)
        {
            int hr = 0;

            // If the IMediaPosition interface exists, use it to set rate
            if (this.mediaPosition != null)
            {
                hr = this.mediaPosition.put_Rate(rate);
            }

            return hr;
        }

    }
}

4/18/2012

Яндекс субботник

Я.Субботник в Киеве, 5 мая

Я.Субботник в Киеве пройдет 5 мая по адресу: Киев, ул. Б.Хмельницкого, 53, отель "Опера", зал "Симфония Гранд Холл".

Регистрация на мероприятие начнется 18 апреля. Количество мест ограничено.

Для тех, кто не попадёт в число участников или не сможет лично присутствовать на Я.Субботнике, будет организована онлайн-трансляция.

Подробную информацию о мероприятии читайте здесь.

4/05/2012

Сайт-справочник организаций и предприятий Киева otzyvchik.com запущен!

Я долго откладывал написание этого сообщения, собирался добавить побольше организаций, написать побольше функционала, но соврешенству нет предела. Поэтому было принято решение запустить сайт таким какой он есть сейчас, пусть люди им начинают пользоваться, и пользователи, то есть вы, сами подскажут, какие организации им наиболее интересны и какие изменения они хотят на сайте увидеть.

Итак, дамы и господа, прошу любить и жаловать - сайт-справочник Киевских предприятий и организаций, и отзывов о них - Отзывчик.

В нынешнем разноообразии заведений и организаций, оказывающих всевозможные услуги, часто бывает сложно сделать выбор: в какой ресторан пойти, где лучше купить свежие продукты, где качественно помоют машину. Озывчик призван помочь в принятии решения, и помогут в этом те люди, которым Вы доверяете - ваши друзья. На отзычике вы можете делиться хорошими и плозими отзывами о люьых заведениях и организациях, добавлять организации и читать отзывы друзей.

Собственно, милости прошу. Заходите, добавляйте организации, пишите отзывы. А если вдруг вам захочется увидеть на сайте что-то новенькое пишите на info@otzyvchik.com.

8/29/2011

Showing text to the right of the input box in Key Survey

Today i have seen a really awesome workaround in one of the Key Survey surveys, where the user has placed the text to the right of the Single Line question input box. Kind of like this:

Note that the word "years" appears to the right of the text input box. And here's hot it's done:

Note that the label for the first answer option is empty and that there is a second answer option with two special tags next to it. Tag <newcolumn/> tells the application that the answer option should appear in the new column (typically this tag is used split the answer options of the same question into multiple columns to prevent scrolling for long lists of answer options. Tag <subheader/> tells that this is not the real answer option and that the application should not show the input box next to it. And both tags used together give you a quick way of showing the text to the right of the input box. (P.S. Some CSS tweaking may be required to make the items appear next to each other, but it is not always required). 

8/28/2011

Converting HTML pages to PDF in pure Java

There are plenty of commercial HTML to PDF converters for the .NET platform (most of which are based on the Internet Explorer libraries that are available in Windows), but HTML to PDF conversion in Java is not that easy. But not impossible. FlyingSaucer allows to convert properly formatted XHTML and CSS2 to PDF (which is in details described here: http://today.java.net/pub/a/today/2007/06/26/generating-pdfs-with-flying-saucer-and-itext.html). However, unfortunately most of the web pages out there are not designed in XHTML. FlyingSuacer when it has to deal with such pages would simply throw an exception and quit. But there also a solution to this limitation, as the developers of FlyingSaucer suggest, we could use one of the existing HTML code cleaners (for example TagSoup, JTidy or HTMLCleaner) for that purpose. Below I would like to show you an example of using HTMLCleaner, FlyingSaucer and iText to convert the HTML to PDF.

package htmltopdf;

import com.lowagie.text.DocumentException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.htmlcleaner.CleanerProperties;
import org.htmlcleaner.CommentNode;
import org.htmlcleaner.HtmlCleaner;
import org.htmlcleaner.HtmlNode;
import org.htmlcleaner.PrettyXmlSerializer;
import org.htmlcleaner.TagNode;
import org.htmlcleaner.TagNodeVisitor;
import org.htmlcleaner.Utils;
import org.xhtmlrenderer.pdf.ITextRenderer;


public class HTMLtoPDF {

    static int cssCounter = 0;
    
    public static void main(String[] args) {
        try {
            final String site = "http://www.keysurvey.co.za";
            final String page = "/company/";
            final String cssUrl = "http://www.keysurvey.co.za";
            
            URL url = new URL(site+page);

            CleanerProperties props = new CleanerProperties();

// HTMLCleaner part
// set some properties to non-default values
            props.setTranslateSpecialEntities(true);
            props.setTransResCharsToNCR(true);
            props.setOmitComments(true);

// do parsing
            TagNode tagNode = new HtmlCleaner(props).clean(url);
            tagNode.traverse(new TagNodeVisitor() {

                public boolean visit(TagNode tagNode, HtmlNode htmlNode) {
                    if (htmlNode instanceof TagNode) {
                        TagNode tag = (TagNode) htmlNode;
                        String tagName = tag.getName();
                        if ("img".equals(tagName)) {
                            String src = tag.getAttributeByName("src");
                            if (src != null && ! src.startsWith("http")) {
                                tag.setAttribute("src", Utils.fullUrl(site, src));
                            }
                        }
                        if ("link".equals(tagName)) {
                            String rel = tag.getAttributeByName("rel");
                            String href = tag.getAttributeByName("href");
                            if (href != null && "stylesheet".equals(rel)) {
                                try {
                                    HttpClient client = new DefaultHttpClient();
                                    String fullUrl = "";
                                    if (href.startsWith("http")) fullUrl = href;
                                    else fullUrl = Utils.fullUrl(cssUrl, href);
                                    HttpGet get = new HttpGet(fullUrl);
                                    HttpResponse response = client.execute(get);
                                    HttpEntity entity = response.getEntity();
                                    if (entity != null) {
                                        InputStream is = entity.getContent();
                                        href = "css" + cssCounter + ".css";
                                        cssCounter++;
                                        OutputStream os = new FileOutputStream(href);
                                        IOUtils.copy(is, os);
                                    }
                                    tag.setAttribute("href", href);
                                } catch (IOException ex) {
                                    Logger.getLogger(HTMLtoPDF.class.getName()).log(Level.SEVERE, null, ex);
                                }
                            }
                        }
                    } else if (htmlNode instanceof CommentNode) {
                        CommentNode comment = ((CommentNode) htmlNode);
                        comment.getContent().append(" -- By HtmlCleaner");
                    }
                    // tells visitor to continue traversing the DOM tree
                    return true;
                }
            });



// serialize to xml file
            new PrettyXmlSerializer(props).writeToFile(
                    tagNode, "page.xhtml", "utf-8");

// FlyingSaucer and iText part
            String inputFile = "page.xhtml";
            String url2 = new File(inputFile).toURI().toURL().toString();
            String outputFile = "firstdoc.pdf";
            OutputStream os = new FileOutputStream(outputFile);

            ITextRenderer renderer = new ITextRenderer();
            renderer.setDocument(url2);
            renderer.layout();
            renderer.createPDF(os);

            os.close();


        } catch (DocumentException ex) {
            Logger.getLogger(HTMLtoPDF.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(HTMLtoPDF.class.getName()).log(Level.SEVERE, null, ex);

        }
    }
}

FlyingSaucer cannot read the CSS files over the web, so we have to save them locally. While parsing the HTML source of the page, for all of the link tags with rel='stylesheet', we need to save a local copy of the css file. Also relative links to the image files need to be replaced with the absolute URLs.

To compile and run the above example you will need the Jars supplied with the FlyingSaucer distribution (it includes XHTMLRenderer and iText), Apache HttpClient, Apache IOUtils and HTMLCleaner.

Overall the code works fairly well on various even poorly formatted pages. There are a few issues that I have noted that may prevent some pages from being rendered to PDF correctly. Here are some of them:
1. Some types of comments (i.e. .ClassName /* some comment */ { ... } ) are not supported by the CSS parser of FlyingSaucer . It simply stops parsing CSS without throwing any exceptions.
2. @import and url() properties of the CSS are not supported.

But overall for many pages the described solution should be goo enough, especially when you need to convert internal pages that you can properly prepare for the PDF conversion.

Добавление кнопки ВКонтакте "Мне нравится" в Blogger

Просмотрел сегодня ряд постов, где люди описывали свои варианты добалвения кнопки ВКонтакте "Мне нравится" к Blogger, но не нашел ни одно 100% работающего варианта. Вернее работающие варинаты сводились к тому, что кнопка работает, когда на одной странице отображается только один пост, в противном сулчае кнопка просто пряталсь. Проблема в том, что скрипт, который по умолчанию предлагает ВКонтакте расчитан на то, что на странице может быть только одна кнопка "Мне нравится".

Итак, вот пошаговая инструкция о том, как показывать кнопку "Мне нравиится" для каждого поста, если у Вас показывается несколько постов на странице.

1. Идем на страницу виджета "Мне нравится" вокнтакте: http://vkontakte.ru/developers.php?o=-1&p=Like и генерируем код с необходимыми настройками.
2. Открываем страницу редактиорвания шаблона в Blogger: Design > Edit HTML. Включаем галочку Expand widget templates:

3. Сразу после тэга вставляем первую часть кода, сгенерированную ВКонтакте. Должно получится нечто вроде:





4. Находим к коде шаблона строку      
<div class="post-footer-line post-footer-line-3">. Ниже этой строки вставляем следюущий код:


Все, теперь внизу каждого вашего поста будет показвать кнопка и работать кнопка "Мне нравится".