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">. Ниже этой строки вставляем следюущий код:


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

5/23/2011

Import images from Picasa web albums to Facebook

I like sharing my photos on Picasa Web Albums: you do not need have an account with Picasa to view the photos, the photos and albums are extremely easy to manage, and the Picasa application itself is probably the best photo management software out there. However, it would be also nice if people on Facebook could see and comment on the photos without having to go to a different site. Until recently, the only proper way to show your Picasa photos on Facebook was to really upload them twice: once to Picasa Web Albums and then to the Facebook albums. Of, course some application existed that made the process of uploading a photo easier, like  the Picasa Uploader - a plugin to the Picasa tool that could upload photos on to Facebook, or Picasa Tab - a Facbook app that allow to create a tab on the profile that would display the Picasa albums (like someone would ever click on that tab),

But today I have found an amazing Facebook app, that does exactly what is needed: it takes a Picasa web album and converts it to the Facebook album. You are also allowed to select individual photos from a Picasa web album and/or upload them into an existing Facebook album instead of creating a new one. The tool is called Picasa Importer.  The process of uploading picture is extremely simple: you provide a link to Picasa album, the tool grabs the photos from it, allows you to select the ones that you want to upload and then creates the Facebook album with the existing photos. Extremely easy and fast.

Hope this will be usedful to someone.

1/03/2011

Распознавание речи при помощи HTK Toolkit, основы

Данная статья является большей частью переводом и адаптацией Tutorial из HTKBook, и представляет собой пошаговое описание построения системы распознавания устного произношения украинских чисел от нуля до девяти. Данная статья дает лишь первое знакомство с HTK Toolkit и позволяет на практике познакомиться с основными компонентами инструментария. Ориентирована статья прежде всего на исследователей, начинающих заниматься проблемами распознавания речи, студентов и аспирантов. Все шаги, описанные ниже могут в принципе быть адаптированы для любого языка. Действия, описанные ниже проводились на операционной системе Windows XP, компиляция HTK Toolkit из исходных кодов проводилась при помощи Visual Studio 2005. Я не буду вдаваться в подробности того, для чего нужна та или иная команда или как они работаю «изнутри», т.к. это достаточно детально описано в HTKBook.

Шаг 1. Скачиваем и устанавливаем HTK Toolkit

HTK Toolkit версии 3.4.1 можно скачать с сайта http://htk.eng.cam.ac.uk/download.shtml
после регистрации на этом сайте.  Регистрируемся на сайте, качаем исходники  HTK, распаковываем их в любую директорию на локальном диске. Самый простой способ собрать HTK, если у вас есть Visual Studio 2003, 2005 или 2008 – при помощи утилиты HTK34xVSxCompile, которую можно скачать отсюда: http://www.co.it.pt/~aveiga/htk/ . Распаковываем архив с HTK34xVSxCompile в директорию, куда прежде был распакован HTK. В директории с HTK появятся два файла: HMMIRest.c и HTK34xVSxCompile.bat. Для сборки достаточно запустить на выполнение HTK34xVSxCompile.bat, и если у Вас в системе есть все необходимое, то сборка пройдет без проблем и в директории с HTK появится новая директория bin.win32, в которой будут все необходимые выполняемые файлы HTK Toolkit. Для того, чтобы создать нашу собственную языковую модель для распознавания, рекомендую скопировать все содержимое из директории bin.win32 в новую директорию, например numbers_UA. Далее, если не указано иначе, все действия производятся в директории numbers_UA, где есть все выполняемые файлы HTK.

Шаг 2. Строим словарь и грамматику.

Система, которую мы строим, должна распознавать любую последовательность украинских чисел от 0 до 9. Для описания грамматики создаем файл grammar со следующим содержимым:

(SENT-START <(ОДИН | ДВА | ТРИ | ЧОТИРИ | П'ЯТЬ | ШІСТЬ | СІМ | ВІСІМ | ДЕВ'ЯТЬ | НУЛЬ)> SENT-END)

Далее необходимо на основе грамматики создать сеть слов (word net). Для этого выполняем команду

HParse.exe grammar wnet

В результате выполнения команды будет создан файл wnet,  в котором будет содержаться  сеть слов.

Следующим шагом руками создаем список слов и словарь. Создаем в текстовом редакторе файл wlist со следующим содержимым:

SENT-END
SENT-START
ВІСІМ
ДВА
ДЕВ'ЯТЬ
НУЛЬ
ОДИН
П'ЯТЬ
СІМ
ТРИ
ЧОТИРИ
ШІСТЬ

Создаем также словарь с фонетической транскрипцией для каждого из приведенных выше слов. В текстовом редакторе создаем файл digits-ua.dic, внутри которого пишем:

SENT-END   [] sil
SENT-START [] sil
ВІСІМ    VV I SS I M sp
ДВА  D V AA sp
ДЕВ'ЯТЬ  D E V J A TT sp
НУЛЬ N U LL sp
ОДИН O D Y N sp
П'ЯТЬ    P J A TT sp
СІМ  SS I M sp
ТРИ  T R YY sp
ЧОТИРИ   CH OE T YY R Y sp
ШІСТЬ    SHI I SS TT sp

Для того, чтобы привести словарь к форме пригодной для HTK Toolkit, необходимо выполнить команду

HDMan.exe –m –w wlist –n monophones1 –l dlog dict digits-ua.dic

Эта команда создаст файл фонетического словаря dict и список фонем monophones1.

Шаг 3. Запись и кодирование обучающих данных.

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

HSGen –l –n 10 wnet dict > trainprompts

Эта команда создаст файл trainprompts со списком из 10 обучающих фраз (можно и больше, но 10 в принципе достаточно для обучения нашей системы). Файл может иметь приблизительно следующий вид:

1. SENT-START ОДИН ТРИ ЧОТИРИ ШІСТЬ ОДИН SENT-END
2. SENT-START ВІСІМ SENT-END
3. SENT-START ВІСІМ СІМ ОДИН ТРИ SENT-END

Записать обучающие фразы можно при помощи программы HSLab, которая входит в состав HTK Toolkit. Для записи каждой из 10 тренировочных фраз необходимо вызвать команду

HSLab <имя записи>

В качестве имени записей можно использовать имена вроде s01, s02,…, s10. У меня не получилось заставить программу HSLab создавать новый файл для новой записи без перезапуска программы, так что для записи каждой новой фразы имеет смысл выйти из HSLab при помощи кнопки Quit и запустить ее заново с новым именем записи. Для записи в программе HSLab надо нажать кнопку Rec, произнести фразу и нажать клавишу Stop. В результате записи в текущей директории будут созданы файлы s01_0, s02_0 и т.д.

После того, как данные записаны необходимо создать файл с описанием записанных данных. Его можно создать руками в текстовом редакторе. Назовем файл words.mlf и запишем в него следующее содержимое:

#!MLF!#
"*/s01.lab"
ОДИН
ТРИ
ЧОТИРИ
ШІСТЬ
ОДИН
.
"*/s02.lab"
ВІСІМ
.
"*/s03.lab"
ВІСІМ
СІМ
ОДИН
ТРИ
.

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

Для следующего шага нам понадобится еще один текстовый файл. Назовем его mkphones0.led. Сгенерируем фонетическую транскрипцию для записанных обучающих данных при помощи команды

HLed.exe –l * -d dict –l phones0.mlf mkphones0.led words.mlf

В результате выполнения будет создан файл phones0.mlf с фонетической транскрипцией для обучающих фраз.

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

TARGETKIND = MFCC_0
TARGETRATE = 100000.0
SAVECOMPRESSED = T
SAVEWITHCRC = T
WINDOWSIZE = 250000.0
USEHAMMING = T
PREEMCOEF = 0.97
NUMCHANS = 26
CEPLIFTER = 22
NUMCEPS = 12
ENORMALISE = F

Список файлов для кодирования задаем в текстовом файле codetr.scp, со следующим содержимым:

s01_0    s01.mfc
s02_0    s02.mfc
s03_0    s03.mfc
s04_0    s04.mfc
s05_0    s05.mfc
s06_0    s06.mfc
s07_0    s08.mfc
s09_0    s09.mfc
s10_0    s10.mfc

Кодирование данных осуществляем при помощи команды

HCopy –T 1 –C config –S codetr.scp


Шаг 4. Обучение скрытых моделей Маркова

В текстовом редакторе создаем файл прототип модели Маркова для системы распознавания названием proto, внутри которого помещаем текст:

~o 39
~h "proto"
     5
     2
         39
              0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
         39
                        1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0
     3
         39
              0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
         39
                        1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0
     4
         39
              0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
         39
                        1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0
     5
         0.0 1.0 0.0 0.0 0.0
         0.0 0.6 0.4 0.0 0.0
         0.0 0.0 0.6 0.4 0.0
         0.0 0.0 0.0 0.7 0.3
         0.0 0.0 0.0 0.0 0.0
               

Также создаем файл со списком закодированных файлов с обучающими данными train.scp:

s01.mfc
s02.mfc
s03.mfc
s04.mfc
s05.mfc
s06.mfc
s08.mfc
s09.mfc
s10.mfc

В файле config первую строку заменяем на

TARGETKIND = MFCC_0_D_A

Далее в текущей директории создаем поддиректории hmm0, hmm1, hmm2, …, hmm9. В последствии в эти директории будут сохранены созданные модели. Создаем прототип модели при помощи команды

HCompV.exe –C config –f 0.01 –m –S train.scp –M hmm0 proto

В результате выполнения в директории hmm0 будут созданы файлы proto и vFloors. В основной директории создаем файл monophones0 копированием файла monophones1 и удалением из него строки sp. В директории hmm0 создаем файл hmmdefs, в котором будут описаны модели для кажой из фонем из файла monophones0, включая sil. Для этого внутри файла hmmdefs для каждой фонемы записываем строку типа

~h "sil"

(в кавычках пишем название фонемы, так как оно записано в monophones0), ниже этой строчки копируем из файла hmm0/proto секцию, начинающуюся со строки <BEGINHMM> и заканчивающуюся строкой <ENDHMM>. Эта секция должна быть одинаковой для каждой фонемы..

Также в директории hmm0 создаем файл macros, в содержимое которого пишем первые три строки из файла hmm0/proto и все содержимое файла vFloors.

После этого последовательно выполняем команды

HERest.exe –C config –I phones0.mlf –t 250.0 150.0 1000.0 –S train.scp –H hmm0/macros –H hmm0/hmmdefs –M hmm1 monophones0

HERest.exe –C config –I phones0.mlf –t 250.0 150.0 1000.0 –S train.scp –H hmm1/macros –H hmm1/hmmdefs –M hmm2 monophones0

HERest.exe –C config –I phones0.mlf –t 250.0 150.0 1000.0 –S train.scp –H hmm2/macros –H hmm2/hmmdefs –M hmm3 monophones0

Следующим шагом к текущим моделям нужно добавить модель короткой паузы. Для этого копируем содержимое директории hmm3 в директорию hmm4, и в файле hmm4/hmmdefs копируем описание модели для фонемы sil и создаем такое же  описание для фонемы sp (~hsp”). В основной директории создаем файл sel.hed с содержимым

AT 2 4 0.2 {sil.transP}
AT 4 2 0.2 {sil.transP}
AT 1 3 0.3 {sp.transP}
TI silst {sil.state[3],sp.state[2]}

И выполняем команду

HHEd.exe –H hmm4/macros –H hmm4/hmmdefs –M hmm5 sil.hed monophones1

После этого уточняем модель командами

HERest.exe –C config –I phones0.mlf –t 250.0 150.0 1000.0 –S train.scp –H hmm5/macros –H hmm5/hmmdefs –M hmm6 monophones1

HERest.exe –C config –I phones0.mlf –t 250.0 150.0 1000.0 –S train.scp –H hmm6/macros –H hmm6/hmmdefs –M hmm7 monophones1

Далее, в файл dict добавляем строку silence sil и создаем уточненную фонетическую транскрипцию, выполнением команды

HVite –l * -o SWT –b silence –C config –a –H hmm7/macros –H hmm7/hmmdefs –I aligned.mlf –m –t 250.0 –y lab –I words.mlf –S train.scp dict monophones1

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

HERest.exe –C config –I aligned.mlf –t 250.0 150.0 1000.0 –S train.scp –H hmm7/macros –H hmm7/hmmdefs –M hmm9 monophones1

HERest.exe –C config –I phones0.mlf –t 250.0 150.0 1000.0 –S train.scp –H hmm8/macros –H hmm8/hmmdefs –M hmm9 monophones1


Шаг 5. Распознавание речи на основе созданной модели.

Создаем текстовый файл config2 копированием файла config. В конце файла config2 дописываем строки:

SOURCERATE=625.0
SOURCEKIND=HAUDIO
SOURCEFORMAT=HTK
ENORMALISE=F
USESILDET=T
MEASURESIL=F
OUTSILWARN=T

Переключаем консоль Windows в кодировку Win1251 (в русскоязычных Windows она по умолчанию 866) командой

chcp 1251

Запускаем программу распознавания речи в «живом» режиме командой

HVite.exe –H hmm9/macros –H hmm9/hmmdefs –C config2 –w wdnet –p 0.0 –s 5.0 dict monophones1

Сразу после запуска программа проведет измерение уровней входящего сигнала. Для этого после отображения приглашения

READY[1]>
Please speak sentence – measuring levels

Необходимо произнести любое предложение. Обычно измерение уровня длится около 4 секунд. По окончанию измерения программа отобразит строку Level measurement completed. Если такая строка не появляется в течение 5-6 секунд, следует остановить программу нажатием Ctrl+C (другого способа я не нашел) и запустить ее заново.

Далее последовательно произносите последовательности чисел, которые система должна распознать и смотрите, как они появляются на экране.