Java - Ứng dụng SAX đọc tin RSS

Public on November 11, 2014

I/- Khảo sát -  Phân tích
Để thấy được ý nghĩa của việc phân tích XML trong ứng dụng thực tiễn; Ở bài viết này, chúng ta sẽ cùng nhau khảo sát và thực hiện lấy tin thông qua kênh RSS của một tờ báo nổi tiếng ở Việt Nam: VNExpresst có địa chỉhttp://www.vnexpress.net.
Trước khi tiến hành khảo sát, ta cần biết một chút về khái niệm RSS. Về cơ bản, RSS là một dạng tài liệu XML được các website hoạt động trên nền tảng Internet cung cấp phục vụ cho mục tiêu chia sẻ thông tin (Hay phát tán nội dung web thông qua trình duyệt). Nhờ RSS, người dùng Internet (bạn đọc) có cơ hội tiếp nhận các tin tức mới một cách nhanh chóng nhất
RSS là thuật ngữ viết tắt (Theo ngôn ngữ English) để chỉ một trong các tiêu chuẩn sau:
-       Rich Site Summary (RSS 0.91)
-       RDF Site Summary (RSS 0.9 and 1.0)
-       Really Simple Syndication (RSS 2.0.0)
Thông tin về RSS, bạn có thể tham khảo thêm tại wikipedia
Trên thực tế, những chương trình được xây dựng để đọc RSS thường được gọi là RSS Feeder (hay aggregator ). Đứng trên góc độ của một lập trình viên, chúng ta sẽ dựa trên kỹ thuật để đọc và phân tích RSS cần tiếp nhận đối với ứng dụng của mình.
Bây giờ ta tiến hành khảo sát thông tin do VNExpress cung cấp thông qua kênh RSS, bạn hãy truy cập VNExpress, sau đó quan sát tại phần đầu trang, tại vị trí ngay dưới Menu, sẽ thấy biểu tượng RSS cung cấp có hình mô tả như dưới đây
Hãy Click vào biểu tượng RSS, bạn sẽ thấy danh mục các kênh RSS do VNExpress cung cấp bao gồm các chủ đề như hình mô tả sau

Bây giờ ta chọn 1 kênh để khảo sát, ví dụ kênh cung cấp tin “Thế giới”. Sau khi click, cấu trúc XML dành cho tin “Thế giới” được mở ra trên trình duyệt với các thông tin như sau
-       Thông tin chung của kênh cung cấp tin tức (Channel) với các thành phần: Tiêu đề (title), Diễn giải cho kênh tin tức (description), ngày xuất bản (pubDate), nơi xuất bản (generator), liên kết cung cấp (link)
-       Nội dung (Các tin thuộc kênh) của channel cung cấp tin bao gồm các thành phần được gọi là item
Hãy quan sát hình mô tả kênh “Thế giới” được cung cấp bởi VNExpress thông qua hình dưới đây (Bạn có thể click vào dấu ở bên trái của các item tag để đóng các item lại và quan sát giống như hình minh hoạ của tôi)
Như chúng ta thấy, mỗi kênh tin do VNExpress cung cấp thường có nội dung gồm 25 tin (Tính tại thời điểm tôi khảo sát và viết bài viết này), mỗi tin tức cung cấp được xác định bởi cặp tag <item> và </item>.
Bây giờ ta lại khảo sát cấu trúc của 1 tin bằng cách click vào ký hiệu  . Ta lại thấy cấu trúc một tin có dạng như sau
-       title: Tiêu đề tin, bài viết
-       description: Diễn giải, nội dung của một tin. ở đây chúng ta thấy phần tóm tắt nội dung của một tin họ dùng dữ liệu CDATA để lưu trữ. Theo bạn thì tại sao phải dùng CDATA cho thành phần này nhỉ ?!...
-       pubDate: Ngày xuất bản, ngày cung cấp tin
-       link: Liên kết trỏ đến bài viết gốc, phần nội dung tin chính đặt tại VNExpress
-       Hai  thành phần: <guid> và <slash:comments> tôi xin phép không giải thích (Tránh làm loãng mục tiêu chính, nếu muốn biết, bạn có thể đặt câu hỏi hoặc tự tìm hiểu thông qua Internet)
II/- Lên kế hoạch – Mục tiêu thực hiện
Tuỳ theo mục tiêu hay yêu cầu mà kỹ thuật tiếp nhận đồng thời phân tích, xử lý XML sẽ được lựa chọn cho phù hợp. Nếu lập trình dựa trên Java, bạn có thể sử dụng SAX hoặc DOM để thực hiện việc này, và trong bài viết này, tôi sẽ áp dụng kỹ thuật phân tích XML dựa trên sự kiện (Event based) SAX để phân tích XML đọc về chương trình từ VNExpress, sau đó chuyển thành các object thể hiện thông tin và trình bày trên trang web của ứng dụng.
Như vậy với thông tin khảo sát ở trên, chúng ta phân tích và lên kế hoạch xây dựng chương trình như sau
-       Thứ nhất: Sau khi tiếp nhận RSS, tôi sẽ chuyển tất cả các item từ XML nhận được thành các object lưu trữ tin tức trong chương trình, như vậy tôi sẽ cần xây dựng 1 class object trong chương trình của mình với prototype như sau
public class item{
    private String itemTitle;
    private 
String itemDescription;
    private 
String itemLink;
    private 
String itemPubDate;
}
-       Thư hai: Do có thể chương trình của chúng ta đọc cùng lúc nhiều kênh (channel), chính vì thế chúng ta cũng sẽ xây dựng class object để đóng gói thông tin của từng channel thành các object riêng biệt với prototype như sau
public class channel{
    private String chanelTitle;
    private 
String chanelDescription;
    private 
String chanelPubDate;
    private 
String chanelLink;
    private 
String chanelGenerator;
    private 
ArrayList<item> items;
}
Trong đó _itemsList là một ArrayList chứa item. Đây chính là danh sách các tin lấy được thuộc Channel tương ứng đọc về thông qua RSS
-       Thư ba: Xây dựng một cơ chế tiếp nhận và phân tích XML dựa trên SAX triển khai từ giao diện contentHandler vớ 3 class cần xây dựng bao gồm
           + RssToChannel : Để tiếp nhận URI cung cấp RSS của VNExpress
           + ProcessRSS_contentHandler: phân tích XML dựa trên SAX (
Triển khai từ giao diện contentHandler)
           + ProcessError_errorHandler: xứ lý tình huống xảy ra lỗi trong quá trình phân tích XML
(
Bạn có thể tham khảo phần minh hoạ kỹ thuật này thông qua bài viết Sử dụng SAX duyệt XML – ContentHandler)
-       Thứ tư: Xây dựng 1 web application dựa trên JSP và Servlet để trình bày tin RSS đọc về dựa trên cơ chế phân tích XML ở trên.
III/- Thực hiện – Sản phẩm
1/- Tạo 1 Java Web application sử dụng framework : Java Server Face
Cấu trúc lưu trữ như sau
2/- Tạo faces-config.xml
3/- Tạo 2 java Class thuộc loại models có nội dung như sau
-       Item class dùng để chứa tin đọc được từ RSS
package models;/**
 * @author Nguyen Mai Huy
 */
public class item {
    private String itemTitle;
    private String itemDescription;
    private String itemLink;
    private String itemPubDate;
    /**
     * @return the itemTitle
     */
    public String getItemTitle() {
        return itemTitle;
    }
    /**
     * @param itemTitle the itemTitle to set
     */
    public void setItemTitle(String itemTitle) {
        this.itemTitle = itemTitle;
    }
    /**
     * @return the itemDescription
     */
    public String getItemDescription() {
        return itemDescription;
    }
    /**
     * @param itemDescription the itemDescription to set
     */
    public void setItemDescription(String itemDescription) {
        this.itemDescription = itemDescription;
    }
    /**
     * @return the itemLink
     */
    public String getItemLink() {
        return itemLink;
    }
    /**
     * @param itemLink the itemLink to set
     */
    public void setItemLink(String itemLink) {
        this.itemLink = itemLink;
    }
    /**
     * @return the itemPubDate
     */
    public String getItemPubDate() {
        return itemPubDate;
    }
    /**
     * @param itemPubDate the itemPubDate to set
     */
    public void setItemPubDate(String itemPubDate) {
        this.itemPubDate = itemPubDate;
    }
}
-       Channel class dùng để chứa thông tin của 1 channel đọc về từ RSS
package models;

import java.util.ArrayList;
/**
 * @author Nguyen Mai Huy
 */
public class Channel {
    private String chanelTitle;
    private String chanelDescription;
    private String chanelPubDate;
    private String chanelLink;
    private String chanelGenerator;
    private ArrayList<Item> items;
    /**
     * @return the ChannelTitle
     */
    public String getChanelTitle() {
        return chanelTitle;
    }
    /**
     * @param chanelTitle the ChannelTitle to set
     */
    public void setChanelTitle(String chanelTitle) {
        this.chanelTitle = chanelTitle;
    }
    /**
     * @return the ChannelDescription
     */
    public String getChanelDescription() {
        return chanelDescription;
    }
    /**
     * @param chanelDescription the ChannelDescription to set
     */
    public void setChanelDescription(String chanelDescription) {
        this.chanelDescription = chanelDescription;
    }
    /**
     * @return the ChannelLink
     */
    public String getChanelLink() {
        return chanelLink;
    }
    /**
     * @param chanelLink the ChannelLink to set
     */
    public void setChanelLink(String chanelLink) {
        this.chanelLink = chanelLink;
    }
    /**
     * @return the ChannelPubDate
     */
    public String getChanelPubDate() {
        return chanelPubDate;
    }
    /**
     * @param chanelPubDate the ChannelPubDate to set
     */
    public void setChanelPubDate(String chanelPubDate) {
        this.chanelPubDate = chanelPubDate;
    }
    /**
     * @return the ChannelGenerator
     */
    public String getChanelGenerator() {
        return chanelGenerator;
    }
    /**
     * @param chanelGenerator the ChannelGenerator to set
     */
    public void setChanelGenerator(String chanelGenerator) {
        this.chanelGenerator = chanelGenerator;
    }
    /**
     * @return the Items
     */
    public ArrayList<Item> getItems() {
        return items;
    }
    /**
     * @param items the Items to set
     */
    public void setItems(ArrayList<Item> items) {
        this.items = items;
    }
}
4/ - Tạo 2 class phục vụ cho việc phân tích XML và xử lý lỗ trong quá trình phân tích
-       ProcessRSS_contentHandler phân tích XML và đóng gói thành item object cung cấp cho channel object trong chương trình. Lớp này implement từ giao diện contentHandler
package helpers;

import models.Channel;
import models.Item;
import java.util.ArrayList;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
/**
 *
 * @author Nguyễn Mai Huy – BODUA Group
 */
public class ProcessRSS_contentHandler implements ContentHandler{

    private Channel channel;
    private 
Item news;
    private 
boolean foundItem;
    private 
StringBuilder value;

    public ProcessRSS_contentHandler(Channel x){
        this.channel = x;
    }
    @Override
    public void setDocumentLocator(Locator locator) {
    }

    @Override
    public void startDocument() throws SAXException {   
    }

    @Override
    public void endDocument() throws SAXException {
    }

    @Override
    public void startPrefixMapping(String prefix, String uri)
                                   throws SAXException {
    }

    @Override
    public void endPrefixMapping(String prefix) throws SAXException {
    }

    @Override
    public void startElement(String uri, String localName,
                 String qName, Attributes atts) throws SAXException {
        this.value new StringBuilder();
        if (qName.equals("item")){
            this.news new Item();
            this.foundItem = true;
        }
    }

    @Override
    public void endElement(String uri, String localName,
                                 String qName) throws SAXException {
        switch (qName.toLowerCase()) {
            case "title":
                if (foundItem)
                    this.news.setItemTitle(this.value.toString());
                else
                 this.channel.setChannelTitle(this.value.toString());
                break;
            case "link":
                if (foundItem)
                    this.news.setItemLink(this.value.toString());
                else
                  this.channel.setChannelLink(this.value.toString());
                break;
            case "description":
                if (foundItem)
                 this.news.setItemDescription(this.value.toString());
                else
                 this.channel.setChannelDescription(
                                    this.value.toString());
                break;
            case "pubdate":
                if (foundItem)
                    this.news.setItemPubDate(this.value.toString());
                else
                    this.channel.setChannelPubDate(
                                             this.value.toString());
                break;
            case "item":
                this.foundItem false;
                this.channel.getItemsList().add(news);
                break;
            case "generator":
                this.channel.setChannelGenerator(
                                             this.value.toString());
                break;
        }
}
    @Override
    public void characters(char[] ch, int start, int length)
                           throws SAXException {
        textInfo.append(ch, start, length);
    }

    @Override
    public void ignorableWhitespace(char[] ch, int start, int length) throwsSAXException {
    }

    @Override
    public void processingInstruction(String target, String data)
                                      throws SAXException {
    }

    @Override
    public void skippedEntity(String name) throws SAXException {
    }
    /**
     * @return the kenhTinTuc
     */
    public Channel getKenhTinTuc() {
        return kenhTinTuc;
    }

}
-       ProcessError_errorHandler triển khai từ giao diện ErrorHandler phục vụ cho xử lý lỗi trong quá trình phân tích XML bằng các in ra thông báo lỗi nhận được từ hệ thống
package helpers;

import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXParseException;
/**
 *
 * @author Nguyễn Mai Huy – BODUA Group
 */
public class ProcessError_errorHandler implements ErrorHandler{

    @Override
    public void warning(SAXParseException exception) {
        showMess("Warning", exception);
    }

    @Override
    public void error(SAXParseException exception) {
        showMess("error", exception);
    }

    @Override
    public void fatalError(SAXParseException exception) {
        showMess("fataError", exception);
    }

    public void showMess(String loaiTB, SAXParseException e){
        System.out.println(loaiTB);
        System.out.println("Lỗi dòng"+e.getLineNumber() +
                           " - Cột thứ:"+e.getColumnNumber()+
                           " Nội dung thông báo: "+e.getMessage());
    }
}
5/- Tạo 1 Class phục vụ cho việc phân tích SAX dựa trên 2 lớp vừa tạo trong phần 4 dựa trên SAXParserFactoryvà XMLReader
package helpers;

import models.Channel;
import java.io.IOException;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
/**
 * @author Nguyen Mai Huy
 */
public class readRSS_ToChanel {
    private Channel tinTuc;

    public readRSS_ToChanel(){
        tinTuc new Channel();
    }
    public readRSS_ToChanel(String strURL){
        tinTuc new Channel();
        try {
            SAXParserFactory spf = SAXParserFactory.newInstance() ;
            SAXParser parser = spf.newSAXParser();
               //---- Tạo reader object đọc dữ liệu thông qua Parser            XMLReader reader = parser.getXMLReader();
            
reader.setContentHandler(new ProcessRSS_contentHandler(tinTuc));
            reader.setErrorHandler(new ProcessError_errorHandler());
               //---- Tạo 1 URL trỏ đến VNExpress.net -------------------------            URL url = new URL(strURL);                  //---- Cung cấp nguồn dữ liệu cho reader thông qua url.OpenStream            InputSource inputs = new InputSource(url.openStream());
            reader.parse(inputs);
        } catch (
IOException | SAXException | ParserConfigurationException ex) {
            Logger.getLogger(rssToChannel.class.getName()).
                                         log(Level.SEVEREnull, ex);
        }
    }
    /**
     * @return the tinTuc
     */
    public Channel getTinTuc() {
        return tinTuc;
    }
    /**
     * @param tinTuc the tinTuc to set
     */
    public void setTinTuc(Channel tinTuc) {
        this.tinTuc = tinTuc;
    }
}
6/- Tạo Managed Bean để tiếp nhận dữ liệu, sử dụng cho giao diện

import helpers.readRSS_ToChanel;
import models.Channel;
import models.Item;
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 *
 * @author Nguyen Mai Huy
 */
public class rssFeedVNExpress {
    private  Channel rssVNE;
    
    /** Creates a new instance of rssFeedVNExpress */    public rssFeedVNExpress() {
        String urlString = "http://vnexpress.net/rss/the-gioi.rss";
        readRSS_ToChanel docRSS = new readRSS_ToChanel(urlString);
        rssVNE = docRSS.getTinTuc();
    }
    /**
     * Hàm tạo thông tin tóm tắt cho item đọc được qua RSS
     * @param i
     * @return
     */
    public String newsSummmary(Item i){
        String kq="";
        kq +="<div class='news'>";
        kq +="   <div class='title'>";
        kq +="      <p style='padding:6px 0;'><a href='"+
                    i.getItemLink() + "' target='_blank' title='" +
                    
i.getItemTitle()+"'>" + i.getItemTitle() + "</a></p>";
        kq +="   </div>";
        kq +="   <p class='pubDate'>" + i.getItemPubDate() + "</p>";
        kq +="   <div class='summary'>";
        kq +="        " + i.getItemDescription();
        kq +="   </div>";
        kq +="</div>";
        return kq;
    }
    /**
     * @return the rssVNE
     */
    public Channel getRssVNE() {
        return rssVNE;
    }
    /**
     * @param rssVNE the rssVNE to set
     */
    public void setRssVNE(Channel rssVNE) {
        this.rssVNE = rssVNE;
    }
}
7/- Lập trình trên trang giao diện để thể hiện kết quả
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
    <h:head>
        <title>RSS Feeder</title>
        <style>
            a{color: #181818; text-decoration:none; }
            .news{width: 222px; height: 368px; display: block;
                  float: left; margin: 8px; overflow: hidden;
                  background: lightyellow; border: #FFFFFF;
                  box-shadow: 3px 6px 12px #383838;
            }
            .news .title{background: brown; color: white;
                         display: block; width: 100%;
                         font-size: 1.1em; padding: 3px;
            }
            .news .title:hover{font-weight: bold; color: yellow;}
            .news .pubDate{color: gray; font-size: 0.8em;
                           font-weight: bold; margin: 0;
                           text-align: right; font-style: italic;
            }
            .news .summary{background: lightyellow; padding: 6px;}
            .news .summary img{width: 100%; }
        </style>
    </h:head>
    <h:body>
        <h1>RSS From VNExpress</h1>
        <p><h:outputText value="#{r.rssChannel.channelTitle}"/> </p>
        <p><h:outputText value="#{r.rssChannel.channelPubDate}"/> </p>
        <p><h:outputText value="#{r.rssChannel.channelGenerator}"/> </p>
        <p><h:outputText value="#{r.rssChannel.channelDescription}"/> </p>
        <c:forEach var="i" items="#{r.rssChannel.itemsList}">
            <h:outputText value="#{r.newsSummary(i)}" escape="false"/>
        </c:forEach>
    </h:body>
</html>
IV/- Kết quả
V/- Yêu cầu dành cho bạn
1 - Hãy tạo 1 Web application làm nhiệm vụ đọc tin RSS dựa trên SAX để lấy tin từ 3 website đưa tin trực tuyến tại Việt Nam bao gồm
-       VNEXpress : http://vnexpress.net/
-       vtcNews : http://vtc.vn/
-       Dân trí : http://dantri.com.vn/
Trên giao diện có biểu tượng của các báo, cho phép người dùng click chọn để cập nhật tin mới nhất trên màn hình

theo: http://www.bodua.vn/


[ Time For Advertisement ]
/**
*  Did you play this game yet, try it now : "Faster Thinking"
*  Download from Google play:  

*  https://play.google.com/store/apps/details?id=com.thanhcs.fasterthinking
**/






[Advertising]Faster Thinking - Speed up your brain


Faster Thinking Game



sentiment_satisfied Emoticon