“Ruby on Rails” with Windows7

Railsを使ってみました。

<僕が使っている環境>
ruby 2.0.0p247 (64bit)
DevKit-mingw64-64-4.7.2-20130224-1432-sfx (64bit)
rails 3.2.14
Windows7 (64bit)


(1)はじめに
はじめ、Rails 4.0を使ってみようと思ったんですが、エラーが出てうまくいきませんでした。atomicというライブラリがインストールできなくて、ストップしてる。

>gem install rails -v 4.0.0
Temporarily enhancing PATH to include DevKit...
Building native extensions.  This could take a while...
ERROR:  Error installing rails:
        ERROR: Failed to build gem native extension.

    C:/work_Apps/Ruby200-x64/bin/ruby.exe extconf.rb
creating Makefile

make "DESTDIR="
generating atomic_reference-x64-mingw32.def
compiling atomic_reference.c
atomic_reference.c: In function 'ir_compare_and_set':
atomic_reference.c:75:2: error: #error No CAS operation available for this platform
make: *** [atomic_reference.o] Error 1

Gem files will remain installed in C:/work_Apps/Ruby200-x64/lib/ruby/gems/2.0.0/gems/atomic-1.1.14 for inspection.
Results logged to C:/work_Apps/Ruby200-x64/lib/ruby/gems/2.0.0/gems/atomic-1.1.14/ext/gem_make.out

いろいろ調べてみたけど、Win7(64bit)がダメというわけでは無さそう。

gemのバージョンが2.0.3以上でないとダメという情報を見つけたけど、僕の環境ではgem2.0.3になっていたので、大丈夫なはず。

ためしにRuby(32bit)でRailsインストールしてみたけど、同じエラーがでる。
しかたなく、Rails3.xxにしてみたら、インストールできました。

というわけで、今回はRails4.xxをあきらめて、Rails3.xxをつかうことにしました。


(2)Ruby、DevKitダウンロード
Rubyのホームページに行きます
オブジェクト指向スクリプト言語 Ruby

「ダウンロード」の「Windows版Rubyバイナリ」から、
One-Click Ruby Installer for Windows を選びます。
ruby01

RubyInstaller for Windows のページでDownloadをクリック。

今回は、Installer版を使わずに、Archives版を使います。なぜかというと、今後、異なるバージョンのRubyを使う可能性があるため、システム環境変数をいじられたくないから。
DevKitも必要になるため、ダウンロードしておきます。

ファイル名:
ruby-2.0.0-p247-x64-mingw32.7z
DevKit-mingw64-64-4.7.2-20130224-1432-sfx.exe
ruby02


(3)Ruby、DevKitインストール
まずRubyインストールします。と言っても、ファイルruby-2.0.0-p247-x64-mingw32.7z を展開し、任意の場所に配置するだけです。

(参考)7z圧縮ファイルの解凍は、僕の場合7-Zip File Managerを使っています。
7-Zip

さて、Rubyインストール先として僕の場合、
C:\work_Apps\Ruby\ruby-2.0.0-p247-x64-mingw32
としました。

Rubyの動作確認します。コマンドプロンプトでPath設定してから、バージョン確認してみます。gemのバージョン確認もしておきます。

>set path="C:\work_Apps\Ruby\ruby-2.0.0-p247-x64-mingw32\bin";%path%

>
>ruby -v
ruby 2.0.0p247 (2013-06-27) [x64-mingw32]

>gem -v
2.0.3

次にDevKitをインストールします。(これがないと、Railsのインストール時にエラーで止まります。)
exeファイルを実行すると、カレントディレクトリに多くのファイルが展開されます。そのため、僕の場合、以下のフォルダを先に作成してそこに展開することにしました。
C:\work_Apps\Ruby\DevKit-mingw64-64-4.7.2-20130224-1432-sfx

そのうえで、DevKit-mingw64-64-4.7.2-20130224-1432-sfx.exe を実行します。ファイルの展開がすんだら、exeファイルは不要になるので、削除しておきます。

次に、DevKitの「dk.rb initを実行」します。すると、DevKitのフォルダにconfig.ymlが作成されます。

>cd C:\work_Apps\Ruby\DevKit-mingw64-64-4.7.2-20130224-1432-sfx

C:\~(省略)~ >ruby dk.rb init

Initialization complete! Please review and modify the auto-generated
'config.yml' file to ensure it contains the root directories to all
of the installed Rubies you want enhanced by the DevKit.

次に、config.ymlに、Rubyのパスを設定します。

  : (中略) :

# Example:
#
# ---
# - C:/ruby19trunk
# - C:/ruby192dev
#
---
- C:\work_Apps\Ruby\ruby-2.0.0-p247-x64-mingw32

次に、「dk.rb install」を実行します。

>cd C:\work_Apps\Ruby\DevKit-mingw64-64-4.7.2-20130224-1432-sfx

C:\~(省略)~ >ruby dk.rb install

[INFO] Updating convenience notice gem override for 'C:\work_Apps\Ruby\ruby-2.0.0-p247-x64-mingw32'
[INFO] Installing 'C:\work_Apps\Ruby\ruby-2.0.0-p247-x64-mingw32/lib/ruby/site_ruby/devkit.rb'

これで、DevKitインストールが環境です。

DevKitを展開したフォルダ(C:\work_Apps\Ruby\DevKit-mingw64-64-4.7.2-20130224-1432-sfx)は、削除してしまっていいのかもしれないけど、よくわからないので、とりあえずそのまま残しています。


(4)Railsインストール
現時点での最新版をバージョン指定して、インストールすることにします。29個のgemがインストールされました。gem list でrailsのバージョンを確認しておきます。

>gem install rails -v 3.2.14
: (中略) :
Installing ri documentation for bundler-1.3.5
Parsing documentation for rails-3.2.14
Installing ri documentation for rails-3.2.14
29 gems installed

>gem list

*** LOCAL GEMS ***

actionmailer (3.2.14)
actionpack (3.2.14)
: (中略) :
rails (3.2.14)
railties (3.2.14)
rake (0.9.6)
rdoc (4.0.0, 3.12.2)
sprockets (2.2.2)
test-unit (2.0.0.0)
: (中略) :

これでRailsインストール完了しました。


(5)Railsアプリ起動
Rails動作確認のため、簡単なアプリを起動してみます。Rails動作確認が目的なので、DBは内蔵されているsqliteを使う設定のままにしておきます。

まず、アプリ作成用の適当な場所を決めます。
僕の場合、Rails_Appsというフォルダを作成して、その中にRailsアプリを置くことにしました。
C:\work_Apps\Ruby\Rails_Apps

次に、Railsアプリ作成ディレクトリに移動して、アプリ作成します。(僕の場合、システム環境設定でPath設定していないので、コマンドでPath設定追加しています)

>set path="C:\work_Apps\Ruby\ruby-2.0.0-p247-x64-mingw32\bin";%path%
>cd C:\work_Apps\Ruby\Rails_Apps
C:\work_Apps\Ruby\Rails_Apps>rails new test01
      create
      create  README.rdoc
      create  Rakefile
      create  config.ru
      create  .gitignore
     : (中略) :
Installing sass-rails (3.2.6)
Installing uglifier (2.2.1)
Your bundle is complete!
Use `bundle show [gemname]` to see where a bundled gem is installed.

アプリ起動してみます。アプリのディレクトリに移動して、起動コマンドを実行します。

>cd test01

C:\work_Apps\Ruby\Rails_Apps\test01>rails server
=> Booting WEBrick
=> Rails 3.2.14 application starting in development on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
[2013-09-24 02:52:54] INFO  WEBrick 1.3.1
[2013-09-24 02:52:54] INFO  ruby 2.0.0 (2013-06-27) [x64-mingw32]
[2013-09-24 02:52:54] INFO  WEBrick::HTTPServer#start: pid=9140 port=3000
[2013-09-24 02:53:24] ERROR LoadError: Please install the sqlite3 adapter: `gem install activerecord-sqlite3-adapter` (sqlite3 is not part of the bundle. Add it to Gemfile.)
        C:/work_Apps/Ruby/ruby-2.0.0-p247-x64-mingw32/lib/ruby/gems/2.0.0/gems/bundler-1.3.5/lib/bundler/rubygems_integration.rb:214:in `block in replace_gem'
        C:/work_Apps/Ruby/ruby-2.0.0-p247-x64-mingw32/lib/ruby/gems/2.0.0/gems/activerecord-3.2.14/lib/active_record/connection_adapters/sqlite3_adapter.rb:3:in `'
     : (中略) :

なんかエラーが出ました。sqlite3が使えないというエラーです。
とりあえずアプリ起動はできていますが、DBへのアクセスができない、という状態です。
ruby03

このエラーの原因としては、どうもバグっぽい。Windows上でRuby(64Bit)を使っているときにおこる?みたい。
解決法としては、以下2つを行います。
・Gemfile.lockの修正
・sqlite3のインストール
(参考)Ruby 2.0.0 – Rails 4.0 に SQLite3 をインストールしたときのメモ [Windows] | superable

まず、アプリのディレクトリ内にあるGemfile.lockを修正します。
C:\work_Apps\Ruby\Rails_Apps\test01\Gemfile.lock

sqlite3 (1.3.8-x86-mingw32) をx64に修正します

  : (中略) :

      multi_json (~> 1.0)
      rack (~> 1.0)
      tilt (~> 1.1, != 1.3.0)
    sqlite3 (1.3.8-x64-mingw32)
    thor (0.18.1)
    tilt (1.4.1)
    treetop (1.4.15)

  : (中略) :

この時点で、Railsアプリ起動しようとしても、sqlite3が無いってエラーが出ます。gem listで確認すると確かにsqlite3がインストールされていませんでした。そのため、sqlite3を手動インストールしました。

C:\work_Apps\Ruby\Rails_Apps\test01>rails server
Could not find sqlite3-1.3.8-x64-mingw32 in any of the sources
Run `bundle install` to install missing gems.

C:\work_Apps\Ruby\Rails_Apps\test01>gem list sqlite3

*** LOCAL GEMS ***

C:\work_Apps\Ruby\Rails_Apps\test01>gem install sqlite3
Fetching: sqlite3-1.3.8-x64-mingw32.gem (100%)
Successfully installed sqlite3-1.3.8-x64-mingw32
Parsing documentation for sqlite3-1.3.8-x64-mingw32
unable to convert "\x90" from ASCII-8BIT to UTF-8 for lib/sqlite3/2.0/sqlite3_native.so, skipping
Installing ri documentation for sqlite3-1.3.8-x64-mingw32
1 gem installed

C:\work_Apps\Ruby\Rails_Apps\test01>gem list sqlite3

*** LOCAL GEMS ***

sqlite3 (1.3.8 x64-mingw32)

あらためて、Railsアプリ起動してみます。

C:\work_Apps\Ruby\Rails_Apps\test01>rails server
=> Booting WEBrick
=> Rails 3.2.14 application starting in development on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
[2013-09-24 03:07:39] INFO  WEBrick 1.3.1
[2013-09-24 03:07:39] INFO  ruby 2.0.0 (2013-06-27) [x64-mingw32]
[2013-09-24 03:07:39] INFO  WEBrick::HTTPServer#start: pid=7248 port=3000

初期画面を確認してみます。アプリの環境情報が見れるようになりました。

http://localhost:3000/
ruby04


(6)Railsの、コントローラ、Viewをお試し
本格的にアプリ作成する前に、DBアクセスなしで、ちょこっと練習してみます。

まずはコントローラ作成します。

「(Railsアプリケーションのルート)」ディレクトリに移動し、次のようにコマンドを実行します。コントローラ名は、sampleにしています。

 C:\work_Apps\Ruby\Rails_Apps\test01>rails generate controller sample
      create  app/controllers/sample_controller.rb
      invoke  erb
      create    app/views/sample
      invoke  test_unit
      create    test/functional/sample_controller_test.rb
      invoke  helper
      create    app/helpers/sample_helper.rb
      invoke    test_unit
      create      test/unit/helpers/sample_helper_test.rb
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/sample.js.coffee
      invoke    scss
      create      app/assets/stylesheets/sample.css.scss

sample_controller.rbを編集します。
test01\app\controllers\sample_controller.rb

# coding: utf-8

class SampleController < ApplicationController

  def hello
    @msg = 'こんにちは。お久しぶりです。';
    render "sample/hello"
  end

  def bye
    render :text => 'bye!'
  end

end

次にビュー作成します。hello.html.erbファイルを自分で作成します。(文字コードUTF8、改行コードLF)
test01\app\views\sample\hello.html.erb

<h1>Hello</h1>
<p>
こんにちは。お元気ですか。<br>
<%= @msg %>
</p>

次に、ルーティング追加します。routes.rbファイルを修正します。
test01\config\routes.rb

以下の2行追加しています。

  (中略)

  # This is a legacy wild controller route that's not recommended for RESTful applications.
  # Note: This route will make all actions in every controller accessible via GET requests.
  # match ':controller(/:action(/:id))(.:format)'

  match 'hello' => 'sample#hello'
  match 'bye' => 'sample#bye'

end

コマンド「rails server」でアプリ起動した後、画面確認してみます。

http://localhost:3000/hello
ruby05

http://localhost:3000/bye
ruby06

ようやくこれで、完了です。

Ruby on Rails のログフォーマット変更

Ruby on Railsのログ出力内容を見てみると、時刻が出力されていません。
時刻とログレベルぐらいは出力しておきたい。っていうか、どこでフォーマット変えれるの?
というわけで、設定してみました。

<僕が使っている環境>
ruby 2.0.0p247 (32bit)
DevKit-mingw64-32-4.7.2-20130224-1151-sfx (32bit)
rails 3.2.14
Windows7 (64bit)


(1)はじめに
[Rubyインストール先]\lib\ruby\gems\2.0.0\gems\activesupport-3.2.14\lib\active_support\core_ext\logger.rb
に、Loggerの定義が書かれているみたいです。

Railsのデフォルトのログフォーマットは、logger.rbの中の、SimpleFormatterで定義されている内容が使われているみたいです。

また、カスタマイズの書き方は、こちらのサイト(Active Support Core Extensions — Ruby on Rails Guides)の、「20.3 datetime_format=」に記載されています。

(参考)
Logger!!! (ログにタイムスタンプを追加する方法) – うんたらかんたらRuby – Rubyist
[道] railsのログへ時刻/PID付与 | 今日もねむひ(-。-)zZZ


(2)やり方その1 とりあえず簡単に!
[RailsアプリのDirectory]/config/environments/development.rb
に、以下の設定を追加する。

  config.logger = Logger.new(config.paths["log"].first)
  config.logger.formatter = Logger::Formatter.new

そうすると、ログ出力内容はこんな感じになります。

I, [2013-11-26T22:38:52.008516 #37856] INFO -- : Started GET "/bye" for 127.0.0.1 at 2013-11-26 22:38:52 +0900
I, [2013-11-26T22:38:52.281531 #37856] INFO -- : Connecting to database specified by database.yml
I, [2013-11-26T22:38:53.385594 #37856] INFO -- : Processing by FinanceController#bye as HTML

#37856というのは、プロセスIDになります。


(2)やり方その1 パート2
[RailsアプリのDirectory]/config/environments/development.rb
に、もうひとつ加えることで、日付のフォーマットをカスタマイズできます。

  config.logger = Logger.new(config.paths["log"].first)
  config.logger.formatter = Logger::Formatter.new
  config.logger.datetime_format = "%Y-%m-%d %H:%M:%S"

ログ出力内容はこんな感じ

I, [2013-11-26 22:54:28#38012] INFO -- : Started GET "/bye" for 127.0.0.1 at 2013-11-26 22:54:28 +0900
I, [2013-11-26 22:54:28#38012] INFO -- : Connecting to database specified by database.yml
I, [2013-11-26 22:54:29#38012] INFO -- : Processing by FinanceController#bye as HTML

(3)やり方その2 フルカスタマイズ
[RailsアプリのDirectory]/config/environment.rb
の中に、フォーマットを定義します。ここでは、FormatWithTimeという名前で定義しています。($$は、pidを表します)

(注)「Initialize the rails application」の後に書くと、エラーになります。

# Load the rails application
require File.expand_path('../application', __FILE__)

class Logger::FormatWithTime < Logger::Formatter
  cattr_accessor(:datetime_format) { "%Y%m%d%H%m%S" }

  def call(severity, timestamp, progname, msg)
    "[#{timestamp.strftime(datetime_format)}.#{'%06d' % timestamp.usec.to_s}] (pida=#{$$}) #{severity} -- : #{String === msg ? msg : msg.inspect}\n"
  end

end

# Initialize the rails application
Test02Mysql::Application.initialize!

その後、
[RailsアプリのDirectory]/config/environments/development.rb
の中で、config.logger.formatter = Logger::FormatWithTime.new
すると、フォーマットが設定されます。

  config.logger = Logger.new(config.paths["log"].first)
  config.logger.formatter = Logger::FormatWithTime.new

ログ出力内容はこんな感じ。

[20131126231146.572204] (pid=39496) INFO -- : Started GET "/bye" for 127.0.0.1 at 2013-11-26 23:22:46 +0900
[20131126231146.883222] (pid=39496) INFO -- : Connecting to database specified by database.yml
[20131126231148.018287] (pid=39496) INFO -- : Processing by FinanceController#bye as HTML

HTML parsing by Ruby

Rubyのライブラリ「Nokogiri」を使って、HTML parsingをやってみることにします。

<僕が使っている環境>
ruby 2.0.0p247 (32bit)
DevKit-mingw64-32-4.7.2-20130224-1151-sfx (32bit)
nokogiri (1.6.0 x86-mingw32)
Windows7 (64bit)


(1)はじめに
もともと僕は、ruby 2.0.0p247 (64bit)を使っていました。ですが、64bit版を使っていると、Nokogiriのインストールで以下のようなエラーが発生して、うまくいきませんでした。

>gem install nokogiri

Fetching: mini_portile-0.5.2.gem (100%)
Successfully installed mini_portile-0.5.2
Fetching: nokogiri-1.6.0.gem (100%)
Temporarily enhancing PATH to include DevKit...
Building native extensions.  This could take a while...
ERROR:  Error installing nokogiri:
        ERROR: Failed to build gem native extension.

    C:/work_Apps/Ruby/ruby-2.0.0-p247-x64-mingw32/bin/ruby.exe extconf.rb
checking for libxml/parser.h... no
-----
libxml2 is missing.  please visit http://nokogiri.org/tutorials/installing_nokogiri.html for help with installing dependencies.
-----
*** extconf.rb failed ***
Could not create Makefile due to some reason, probably lack of necessary
libraries and/or headers.  Check the mkmf.log file for more details.  You may
need configuration options.

Provided configuration options:
        --with-opt-dir
        --without-opt-dir
  :   :
   (中略)

Gem files will remain installed in C:/work_Apps/Ruby/ruby-2.0.0-p247-x64-mingw32/lib/ruby/gems/2.0.0/gems/nokogiri-1.6.0 for inspection.
Results logged to C:/work_Apps/Ruby/ruby-2.0.0-p247-x64-mingw32/lib/ruby/gems/2.0.0/gems/nokogiri-1.6.0/ext/nokogiri/gem_make.out

libxml2が見つからないというエラーだったので、libxml2のインストール方法などなどを調べてみたものの、結局、解決しませんでした。

で、別の方向でいろいろ調べていたら、
Nokogiri does not yet support the 64-bit version of Ruby 2.0.0.
という情報を発見。
(参考) http://stackoverflow.com/questions/17280884/cant-install-nokogiri-for-ruby-in-windows
(参考) http://stackoverflow.com/questions/15320462/error-installing-nokogiri

そこで、32bit版のRuby2.0.0にしてみたら、Nokogiriのインストールがうまくいきました。
僕が使っているのは、「One-Click Ruby Installer for Windows」版です。
ruby_installer


(2)Nokogiriのインストール
gemでインストールします。
すると、以下の2つのライブラリがインストールされました。
mini_portile (0.5.2)
nokogiri (1.6.0 x86-mingw32)

> gem install nokogiri

Fetching: mini_portile-0.5.2.gem (100%)
Successfully installed mini_portile-0.5.2
Fetching: nokogiri-1.6.0-x86-mingw32.gem (100%)
Successfully installed nokogiri-1.6.0-x86-mingw32
Parsing documentation for mini_portile-0.5.2
Installing ri documentation for mini_portile-0.5.2
Parsing documentation for nokogiri-1.6.0-x86-mingw32
unable to convert "\x90" from ASCII-8BIT to UTF-8 for lib/nokogiri/1.9/nokogiri.so, skipping
unable to convert "\x90" from ASCII-8BIT to UTF-8 for lib/nokogiri/2.0/nokogiri.so, skipping
Installing ri documentation for nokogiri-1.6.0-x86-mingw32
2 gems installed

> gem list

*** LOCAL GEMS ***

bigdecimal (1.2.0)
io-console (0.4.2)
json (1.7.7)
mini_portile (0.5.2)
minitest (4.3.2)
nokogiri (1.6.0 x86-mingw32)
psych (2.0.0)
rake (0.9.6)
rdoc (4.0.0)
test-unit (2.0.0.0)

(3)使ってみる
とりあえず、Twitterのつぶやきを取ってみようかと思います。誰でもいいんだけど、今回は、孫正義さんのツイートにしてみます。

URLは、https://twitter.com/masason となり、HTTPSでアクセスすることになります。
しかし、

page=open('https://twitter.com/masason')

として、アクセスすると、SSLのエラーが発生します。

SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed (OpenSSL::SSL::SSLError)

そのため、今回は、「証明書を検証をしない」設定でアクセスすることにしました。
(参考) mofu犬blog: [Ruby] open-uri の HTTPS リクエストで certificate verify failed:
(参考) mofu犬blog: [Ruby] デフォルトの CA 証明書ファイルを変更しても、証明書の検証に失敗する:

あと、文字コードも指定しています。

require 'nokogiri'
require 'open-uri'
require 'openssl'

page=open('https://twitter.com/masason', :ssl_verify_mode => OpenSSL::SSL::VERIFY_NONE)
html = Nokogiri::HTML(page.read, nil, 'UTF-8')

次に、取得したいツイート内容が、HTMLソースの中でどうなっているか見てみます。
ツイッターのWeb画面のHTMLソースから、ツイート1件分のところを見てみると、以下のような感じになっています。
tweet_html

<div class=”content”> タグの中に、ツイートの時刻と本文が収まっています。
その中の、<small class=”time”>タグの中の、<a>タグの中の、title属性に、時刻が入っています。
そして、<p class=”js-tweet-text tweet-text”>タグの中に、ツイート本文が入っています。

これらを踏まえてコーディングするとこんな感じ。

html.search('div.content').each do |content|
  # tweet時間の表示
  a=content.css('small.time a').first
  puts a.attribute("title").value

  # tweet本文の表示
  p=content.css('p.js-tweet-text.tweet-text').first
  puts p.content
end

(4)コーディングまとめ
最終的に作成したコードは、以下のようになりました。
僕の場合、AptanaStudioで動かしてるので、1行目の「#!/usr/bin/ruby」は適当です。

#!/usr/bin/ruby
# -- coding: utf-8

require 'nokogiri'
require 'open-uri'
require 'openssl'

page=open('https://twitter.com/masason', :ssl_verify_mode => OpenSSL::SSL::VERIFY_NONE)
html = Nokogiri::HTML(page.read, nil, 'UTF-8')

html.search('div.content').each do |content|
  puts "-" * 30

  # tweet時間の表示
  a=content.css('small.time a').first
  puts a.attribute("title").value

  # tweet本文の表示
  p=content.css('p.js-tweet-text.tweet-text').first
  puts p.content

end

実行するとこんな感じで、標準出力に返ってきます。

------------------------------
2013年10月22日 - 7:39
昨日、我が社の笠井(76歳)さんが亡くなられた。
笠井さんは、私の父の様な兄の様な方。企業買収や投資に必要な財務戦略や、資金調達に多大な力を発揮。笠井さん無しに今日のソフトバンクは存立していない。心より感謝し、ご冥福をお祈りいたします。
------------------------------
2013年10月15日 - 6:18
夢さえあれば朝起きるのが楽しい。
------------------------------
2013年10月14日 - 7:42
物事の解決策は常に複数ある。
一つにこだわり過ぎてはならない。
  :    :

JavaでRSSの広告削除してみる(3) Removing Ads from Rss by Java

RSSから広告削除する、簡単なアプリをつくってみようかと思っています。
3回に分けて書いています。I write this in 3-posts.

第1回 JavaでRSS受信する。 Getting RSS by Java
第2回 HTMLパーサを使う。 Using HTML-parser
第3回 RSSを出力してみる。 Outputting RSS

今回は第3回めです。This is 3rd post.

————————————————————–
使っている環境

Java JDK1.6.0_35(64bit版)
Google App Engine SDK for Java 1.7.1
Eclipse IDE for Java EE Developers_ Juno (4.2)64Bit版
The Google Plugin for Eclipse, for Eclipse 3.8/4.2 (Juno)

ROME 1.0
JDOM 1.1.3
( Xerces 2.11.0 )
jsoup-1.7.2


(1)はじめに Introduction
クラスを作ってその中で「RSS読込→広告削除→RSS出力」の処理を行いたいと思います。
RSS読込・広告削除は、前回・前々回のエントリーで行ったので、今回は、まずRSS出力を試してみます。

In my Class, I would try to do entire process of “RSS-reading”, “removing-Ads”, and “RSS-outputting”.
I did “RSS-reading” and “removing-Ads” in previously post. Today, I will try to do “RSS-outputting” first.


(2)RSSの出力 Outputting RSS
SyndFeedOutput クラスのoutputString メソッドで、XMLを出力できます。
ROMEでRSSを読込んだ時のfeedを、outputString メソッドに引き渡す感じになります。
You could output XML by using outputString method of SyndFeedOutput class.
You execute outputString method with the “feed” that made by ROME .
(参考) 技術への名残り: JavaでRSSを作成(ROME利用)

        SyndFeedOutput output = new SyndFeedOutput();
        String outputRss = output.outputString(feed);

(3)コーディング Coding
CrawlRssクラスを作って、その中で処理を行うことにしました。
処理の流れはこんな感じ。
I thought that I would make “CrawlRss class” and do what’s I need.
I will show you my plan(below)

・setUrlメソッド
接続先URLをセットします。Setting URL

・getRssFeedメソッド
RSSを読込みます。このメソッドは、取得したデータ(SyndFeed型)を返します。
Reading RSS. this method will return that data as SyndFeed-type.

・doFilterメソッド
RSSのタイトルが、広告かどうかをチェックして、広告の場合は削除します。
RSSのdescriptionをチェックして、中に含まれる広告を削除します。
(広告かどうか判別する処理は、今後も増えていくため、別メソッドに書いています。
タイトルのチェックはtitleIsAdsメソッド、descriptionのチェックはhtmlParsingメソッドの中で、具体的なチェックを行っています。)

Checking “Title” of RSS-entry. and If the Title is Ads, it’s remove the RSS-entry.
Checking “description” of RSS-entry. And it’s remove Ads in the description.

・outputRssメソッド
RSSfeedデータから、RSSを出力します。String型でXMLデータを返します。
This output RSS generated from RSSfeed-data. This return RSS as XML-data.

CrawlRss.java

package rssReader;

import java.io.IOException;
import java.net.URL;
import java.util.Iterator;
import java.util.List;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.io.FeedException;
import com.sun.syndication.io.SyndFeedInput;
import com.sun.syndication.io.SyndFeedOutput;
import com.sun.syndication.io.XmlReader;
import com.sun.syndication.feed.synd.SyndContent; 

public class CrawlRss {
    private String url;

    /** 
     * XXX 
     *
     * @param  url  URL for getting RssFeed
     * @param  feed  RssFeed generated by ROME
     */
    public void setUrl(String url) {
        this.url = url;
    }

    public String getUrl() {
        return this.url;
    }

    public SyndFeed getRssFeed() throws IOException  {
        SyndFeed feed = null;

        if ( this.url == null ){
            System.out.println("Parameter( url ) didn't set.");
            return null;
        }else{
            try {
                URL feedUrl = new URL(this.url);
                XmlReader reader = new XmlReader(feedUrl.openStream());
                    SyndFeedInput input = new SyndFeedInput();
                    feed = input.build(reader);
                reader.close();

            } catch (FeedException e) {
                e.printStackTrace();
            }
        }

        return feed;
    }

    public SyndFeed doFilter(SyndFeed feed){

        List<SyndEntry> entries = feed.getEntries();
        Iterator<SyndEntry> itr = entries.iterator();

        while (itr.hasNext()){
            SyndEntry entry = itr.next();

            // Removing Ads from Title
            String title = entry.getTitle();
            if ( titleIsAds(title)) {
                itr.remove();
                continue;
            }

            // Removing Ads from Desc
            SyndContent desc = entry.getDescription();

            if (desc != null){
                String html_parsed = htmlParsing(desc.getValue());
                desc.setValue(html_parsed);
            }
        }

        return feed;
    }

    public boolean titleIsAds(String title){
        if ( title.startsWith("PR:")) {
            return true;
        }else{
            return false;
        }

    }

    public String htmlParsing(String html){

        Document doc = Jsoup.parse(html, "UTF-8");
        Elements links = doc.getElementsByTag("a");

        for (Element link : links) {
            String href = link.attr("href");

            if ( href.indexOf("rss.rssad.jp/rss/ad/") != -1 ) {
                link.remove();
            }
        }

        Elements imgs = doc.getElementsByTag("img");

        for (Element img : imgs) {
            String width = img.attr("width");
            String height = img.attr("height");

            if ( width.equals("1") && height.equals("1") ) {
                img.remove();
            }
        }

        String bodies_str = doc.getElementsByTag("body").toString();
        bodies_str = bodies_str.replaceFirst("^<body>\n", "");
        bodies_str = bodies_str.replaceFirst("\n</body>$", "");
        bodies_str = bodies_str.replaceFirst("^ +", "");

        return bodies_str;
    }

    public String outputRss(SyndFeed feed) throws FeedException{
        SyndFeedOutput output = new SyndFeedOutput();
        String outputRss = output.outputString(feed);

    return outputRss;
    }
}

実際に動かす場合は、こうします。
Here is my code to use the Class.

package rssReader.test;

import java.io.IOException;

import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.io.FeedException;

import rssReader.CrawlRss;

public class unitTest_output_XML {
    public static void main(String[] args) throws IOException, FeedException {

        CrawlRss crss = new CrawlRss();

        crss.setUrl("http://rss.rssad.jp/rss/gihyo/feed/rss2");
        // crss.setUrl("http://jp.techcrunch.com/feed/");
        // crss.setUrl("http://wired.jp/rssfeeder/");

        //System.out.println("Access to : " + crss.getUrl() + "\n\n");

        SyndFeed feed = crss.getRssFeed();

        if (feed == null){
            System.out.println("Exit");
            System.exit(-1);
        }

        feed = crss.doFilter(feed);

        String outputRss = crss.outputRss(feed);
        System.out.println(outputRss);

    }

}

こんな感じでXMLが返ってきます
That code will return these XML(below).

<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0">
  <channel>
    <title>gihyo.jp:総合</title>
    <link>http://gihyo.jp/</link>
    <description>gihyo.jp(総合)の更新情報をお届けします</description>
    <language>ja-jp</language>
    <copyright>技術評論社 2013</copyright>
   :   :

    <item>
      <title>第6回 Capacity Scheduler による複数ジョブの同時実行 ── halookで始めるHadoop/HBaseトラブルシューティング</title>
      <link>http://rss.rssad.jp/rss/artclk/47ghQF72z411/6d70ced8b37632c22bd6add0ae4e4982?ul=tMUp6phKmaEl5PP2lXYGvZcEI0UboxMPKoYv1ONSRKgTtDvugPJtMP3M.QKxrTI5bkjb3.g</link>
      <description>今回は,Capacity Schedulerを使った場合について,複数ジョブの同時実行時の挙動を見てみましょう。</description>
   :   :

    </item>
    <item>
   :   :

今のところ、TechCrunch Japan、WIRED.jp、gihyo.jp のRSSfeedから広告削除できるようになっています。もうちょっと、チェックの条件を増やして、たいていのRSSに対応できるような形にできればいいなと、思っています。
This code supports removing Ads from RSS of “TechCrunch Japan”, “WIRED.jp” “gihyo.jp”.
I hope that I would add more rule to the code for removing Ads, so it could support more RSS.

それをGoogleAppEngine上で動かすようにすれば、APIとして公開できるかな。(同じようなサービスは、たくさんあるけど(笑))
And then,  I would deploy the code on GAE as an API. (There is many Service for Romoving Ads  from RSS :-)  )

JavaでRSSの広告削除してみる(2) Removing Ads from Rss by Java

RSSから広告削除する、簡単なアプリをつくってみようかと思っています。
3回に分けて書いています。I write this in 3-posts.

第1回 JavaでRSS受信する。 Getting RSS by Java
第2回 HTMLパーサを使う。 Using HTML-parser
第3回 RSSを出力してみる。 Outputting RSS

今回は第2回めです。This is 2nd post.

————————————————————–
使っている環境

Java JDK1.6.0_35(64bit版)
Google App Engine SDK for Java 1.7.1
Eclipse IDE for Java EE Developers_ Juno (4.2)64Bit版
The Google Plugin for Eclipse, for Eclipse 3.8/4.2 (Juno)

jsoup-1.7.2


(1)はじめに Introduction
実際にRSS取得してみると、記事丸ごとが広告になっている場合と、要約の中に広告が埋まっている場合があります。
たとえば、タイトルが”PR:”で始まっているものは、記事全体が広告になってる。これはタイトルの文字列をチェックするだけなので取り除くのは簡単です。
I get RSS, then I found that some article are Ads, and some description contain Ads.
For example, if “Title” start with “PR:”, this articl is Ads. Checking Title is easy.

PR: 医療機関ホームページの虚偽・誇大な表現はNGです!

要約の中に広告が埋まっている場合は、以下のような形で、広告のリンクが埋まっています。
また、サイズが1×1の画像ファイルが埋め込まれていたりします。これも要らない。
This is example that a description contain “link to Ads”.  And there is “img” that it has 1x1size. this “img” have to be removed too.

<p>前回紹介したように,gmtpに文字コード回りのパッチをあてることで,無事Plamo Linux環境とNexus 7の間でファイルをやりとりできるようになりました。しかしながら,あれこれ使っているとgmtpの欠点も目についてきました。
<img border="0" width="1" height="1" src="http://rss.rssad.jp/rss/artimg/47ghQF72z411/3184e1aa6c761b751f68c2613f8a3f55"/></p>
<br clear="all" /><a href="http://rss.rssad.jp/rss/ad/47ghQF72z411/pFjyc8QFo.Eh?type=1" target="_blank"><img src="http://rss.rssad.jp/rss/img/47ghQF72z411/pFjyc8QFo.Eh?type=1" border="0"/></a><br/>

これらを取り除く場合、HTMLをParseする必要があるため、HTMLパーサのライブラリを使い、不要な部分のみを取り除くことにします。
You should use HTMP-parsing-library to remove these words.


(2)jsoupのダウンロード Downloading jsoup
そもそもHTMLのパーサは、たくさんあります。There are many library.
Comparison of HTML parsers – Wikipedia, the free encyclopedia

活発に開発されていてHTML5にも対応しているものがいいと思いました。
jsoupはHTML5にも対応しており、jQuery-likeなやり方でも使えるとのことで、なかなかよさそうです。
なお、Unitテストといったことを行いたい場合は、HtmlUnitがいいみたいです。
I should use the library that is developed more active , and support  HTML5.
“jsoup” support HTML5, and jQuery-like method. I think it’s ok.
If you wish to do Unit-test , HtmlUnit would be suitable.
What are the pros and cons of the leading Java HTML parsers? – Stack Overflow
HtmlUnit vs JSoup: HTML Parsing in Java | Javalobby

今回はHTMLのparsingが目的なので、jsoupを使うことにしました。
こちらのサイトから、jsoup-1.7.2.jar をダウンロードし、Eclipseのライブラリに追加します。
I wish to use library for parsing HTML. So I chose jsoup.
you can download jsoup-1.7.2.jar in this site. And you should add it to Eclipse-library .
jsoup Java HTML Parser, with best of DOM, CSS, and jquery


(3)とりあえず読込んでみる Reading simply
テストデータとして以下の内容を、テキストファイル(testdata_html.txt)に書き込んでおき、jsoupを動かしてみました。
I tried to use jsoup. I made test-data in a text file(testdata_html.txt). I show you the test-data below.

<p>前回紹介したように,gmtpに文字コード回りのパッチをあてることで,無事Plamo Linux環境とNexus 7の間でファイルをやりとりできるようになりました。しかしながら,あれこれ使っているとgmtpの欠点も目についてきました。
<img border="0" width="1" height="1" src="http://rss.rssad.jp/rss/artimg/47ghQF72z411/3184e1aa6c761b751f68c2613f8a3f55"/></p>
<br clear="all" /><a href="http://rss.rssad.jp/rss/ad/47ghQF72z411/pFjyc8QFo.Eh?type=1" target="_blank"><img src="http://rss.rssad.jp/rss/img/47ghQF72z411/pFjyc8QFo.Eh?type=1" border="0"/></a><br/>

基本的に公式ドキュメントがとてもわかりやすかったので、それを参考にしました。
I check this documents for use .
Cookbook: jsoup Java HTML parser

jsoupでテストデータを読込むと、不足している要素・タグが追加され、HTMLファイルの構成で、内部に格納されます。<html>タグや、<head>タグが追加されます。
When jsoup get test-data, jsoup make entire HTML elements. If test-data didn’t have “<html>-tag” or “<head>-tag”, jsoup would add these tags.

parsingした結果をブラウザで確認してみようと思ったので、<head>タグ内に文字コード設定を追加することにしました。
で、とりあえずテストデータを読込んで、それをそのままjsoupで出力させるコードを以下に書きます。
I think I practised jsoup and I would check the result by Browser. So I added char-setting in “<head>-tag”.
If you execute this code, it’s show you the result of “Reading RSS” and “outputting RSS-parsing-data”.

HtmlParsing.java

import java.io.File;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;

public class HtmlParsing {
    public static void main(String[] args) throws Exception {

        File input = new File("testdata_html.txt");
        Document doc = Jsoup.parse(input, "UTF-8");

        Element head = doc.select("head").first();
        head.html("<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">");

        System.out.println("--- doc -----");
        System.out.println(doc + "\n");

        System.out.println("--- doc text only-----");
        System.out.println(doc.text() + "\n");

    }
}

実行結果は、こんな感じになります。
You can get this when you execute that code(above).

--- doc -----
<html>
 <head>
  <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
 </head>
 <body>
  <p>前回紹介したように,gmtpに文字コード回りのパッチをあてることで,無事Plamo Linux環境とNexus 7の間でファイルをやりとりできるようになりました。しかしながら,あれこれ使っているとgmtpの欠点も目についてきました。 <img border="0" width="1" height="1" src="http://rss.rssad.jp/rss/artimg/47ghQF72z411/3184e1aa6c761b751f68c2613f8a3f55" /></p> 
  <br clear="all" />
  <a href="http://rss.rssad.jp/rss/ad/47ghQF72z411/pFjyc8QFo.Eh?type=1" target="_blank"><img src="http://rss.rssad.jp/rss/img/47ghQF72z411/pFjyc8QFo.Eh?type=1" border="0" /></a>
  <br />
 </body>
</html>

--- doc text only-----
前回紹介したように,gmtpに文字コード回りのパッチをあてることで,無事Plamo Linux環境とNexus 7の間でファイルをやりとりできるようになりました。しかしながら,あれこれ使っているとgmtpの欠点も目についてきました。

(4)Parsing
広告の削除と、サイズが1×1の画像ファイルの削除を行います。
I will remove Ads and img-files that has 1x1size.

<a>タグの一覧を取得し、それぞれのAttribute”href”の値を取得します。その中に”rss.rssad.jp/rss/ad/”が含まれていたら、<a>タグごと削除します。これで広告の削除ができます。
This code gets <a>-tags and each “href” Attribute. If there are the words “rss.rssad.jp/rss/ad/”, it’s remove entire <a>-tag. Then you could remove ads.

また、<img>タグの一覧を取得し、それぞれのAttribute”width”、”height”の値を取得します。width=height=1の場合、<img>タグごと削除します。
And it’s gets <img>-tags, each “width” Attribute and each “height” Attribute. If those Attribute are width=height=1, you chould remove entire <img>-tag.

ソースコードは、こんな感じになりました。
this is my code.

HtmlParsing2.java

import java.io.File;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

public class HtmlParsing2 {
    public static void main(String[] args) throws Exception {

        File input = new File("testdata_html.txt");
        Document doc = Jsoup.parse(input, "UTF-8");

        Element head = doc.select("head").first();
        head.html("<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">");

        Elements links = doc.getElementsByTag("a");

        for (Element link : links) {
            String href = link.attr("href");

            // System.out.println("href : " + href);
            // System.out.println("link.text : " + link.text());
            // System.out.println("link : " + link);

            if ( href.indexOf("rss.rssad.jp/rss/ad/") != -1 ) {
                link.remove();
            }
        }

        Elements imgs = doc.getElementsByTag("img");

        for (Element img : imgs) {
            String width = img.attr("width");
            String height = img.attr("height");
            // System.out.println("img : " + img);

            if ( width.equals("1") && height.equals("1") ) {
                img.remove();
            }
        }

        System.out.println("--- doc2 -----");
        System.out.println(doc + "\n");
        System.out.println("--- doc text only-----");
        System.out.println(doc.text() + "\n");
        System.out.println("--- body-----");
        System.out.println(doc.getElementsByTag("body") + "\n");
    }
}

実行結果は、こんな感じ。
I will how you the result.

--- doc2 -----
<html>
 <head>
  <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
 </head>
 <body>
  <p>前回紹介したように,gmtpに文字コード回りのパッチをあてることで,無事Plamo Linux環境とNexus 7の間でファイルをやりとりできるようになりました。しかしながら,あれこれ使っているとgmtpの欠点も目についてきました。 </p> 
  <br clear="all" />
  <br />
 </body>
</html>

--- doc text only-----
前回紹介したように,gmtpに文字コード回りのパッチをあてることで,無事Plamo Linux環境とNexus 7の間でファイルをやりとりできるようになりました。しかしながら,あれこれ使っているとgmtpの欠点も目についてきました。

--- body-----
<body>
 <p>前回紹介したように,gmtpに文字コード回りのパッチをあてることで,無事Plamo Linux環境とNexus 7の間でファイルをやりとりできるようになりました。しかしながら,あれこれ使っているとgmtpの欠点も目についてきました。 </p> 
 <br clear="all" />
 <br />
</body>

これで、やりたいことはできました。
Then I did.

最終的にRSSのフィードに戻すときに、<body>, </body>タグが不要になります。なので、以下のようにして、Elements型を文字列にしてから取り除くことにします。(※不要な改行と、先頭の空白文字も取り除いています。)
When you return this result to RSS-feed, <body>tag and </body>-tag didin’t need.
So I transfered type-“elements” to String, and then removed these elements . ( I removed “\n” and “blanks at the head of line”)

     :   :
        String bodies_str = doc.getElementsByTag("body").toString();
        bodies_str = bodies_str.replaceFirst("^<body>\n", "");
        bodies_str = bodies_str.replaceFirst("\n</body>$", "");
        bodies_str = bodies_str.replaceFirst("^ +", "");
     :   :

とりあえず、これで下準備がおわりました。
次回のエントリーでは、「RSS読込→広告削除→RSS出力」までつなげてみます。

In next post, I would try to do entire process of “RSS-reading”, “removing-Ads”, and “RSS-outputting”.

JavaでRSSの広告削除してみる(1) Removing Ads from Rss by Java

IT関連の情報サイトから、RSSフィードの情報収集して、その中の単語を解析・集計して、自分用のトレンドランキングをつくろうかと思った。
I come up with making “trend word list” for myself. I try to collect RSS from IT-website, analyze RSS-feed, and rank it.

ためしにRSS受信してみると、RSSフィードの中に広告が入っている場合がある。広告は削除しておきたい。Some RSS contain advertisement. I hope to remove Ads.

RSSから広告削除してくれるサービスがるんだけれども、There are some service to remove Ads from RSS.
RSS広告を削除ならRSS広告削除社

gihyo.jpのRSSの広告がうまく削除されなかった。But, that service didn’t remove Ads from “gihyo.jp”.
というわけで、自分でつくってしまおうかと思った。So I think of trying to make app.

3回に分けて書いていきます。I write this in 3-posts.

第1回 JavaでRSS受信する。 Getting RSS by Java
第2回 HTMLパーサを使う。 Using HTML-parser
第3回 RSSを出力してみる。 Outputting RSS

今回は第1回めです。This is first post.

————————————————————–
使っている環境

Java JDK1.6.0_35(64bit版)
Google App Engine SDK for Java 1.7.1
Eclipse IDE for Java EE Developers_ Juno (4.2)64Bit版
The Google Plugin for Eclipse, for Eclipse 3.8/4.2 (Juno)

ROME 1.0
JDOM 1.1.3
( Xerces 2.11.0 )


(1)はじめに Introduction
JavaのRSSライブラリには、いろいろあるみたい。
There are many library for RSS-reading.
Java や Ruby の RSS/Atom feed ライブラリなど。HTMLからRSSを自動生成……したいなぁ。 (RSS – MemoWiki)

いくつか見てみたんだけど、RSSライブラリはあんまり活発な開発がされてない雰囲気。
どうも、RSSは、昔から使われていて仕様がかたまっているため、ライブラリの改修をする必要があんまり無いっぽい。
で、なんとなく情報量の多そうな、ROMEを使うことにした。
I search RSS-library. But some RSS-library is not active.
I think that RSS-format is so old, and changeless. So, it’s no need to modify RSS-library.


(2)ROME, JDOMのダウンロード  Downloading “ROME and JDOM”
こちらのサイトから、ROMEのダウンロードを行います。僕の場合、rome-1.0.jarをダウンロードしました。
You can download ROME from this site. I downloaded rome-1.0.jar.
Home – ROME – Confluence

ROMEはJDOMと依存関係があるため、こちらのサイトからJDOMもダウンロードします。
ROME have to use with JDOM. So, you should download JDOM from thi site.
JDOM

現時点でJDOM 2.0.5が最新版でしたが、こちらを使うとROMEがうまく動きませんでした。
なので、JDOM 1.1.3を使う必要があります。ダウンロードしたファイルjdom-1.1.3.zipの中のbuildフォルダに、jdom-1.1.3.jarがあるので、これをEclipseのライブラリに追加します。
“JDOM’s latest version 2.0.5” don’t work with ROME. You should use JDOM 1.1.3.
Download jdom-1.1.3.zip, then there is jdom-1.1.3.jar in “build” folder. And add jdom-1.1.3.jar to Eclipse-library.


(3)Xercesのダウンロード Downloading Xerces
こちらは、必要に応じて使うものになります。
単体のJavaアプリとして動かす際には必要ないですが、GAE上でROMEを動かそうとすると、以下のようなエラーが出ます。

Xerces dosn’t need, if you don’t make your Apps on GAE.
But if you use ROME on GAE, you would face this error.

java.lang.IllegalStateException: JDOM could not create a SAX parser
    at com.sun.syndication.io.WireFeedInput.createSAXBuilder(WireFeedInput.java:328)
    at com.sun.syndication.io.WireFeedInput.build(WireFeedInput.java:189)
    at com.sun.syndication.io.SyndFeedInput.build(SyndFeedInput.java:123)
   :   :
   :   :

原因は謎。こちらのサイトによると、Xercesが必要とのこと。
I don’t know Why this error has happen. According to these site, you need Xerces.
Romeが使えない – Google グループ
Issue 1367 – googleappengine – org.apache.xerces.parsers.SAXParser not available – Google App Engine – Google Project Hosting

そのため、こちらのサイトからXercesをダウンロードします。
You can download Xerces from this site.
The Apache Xerces™ Project – xerces.apache.org

ダウンロードしたファイル Xerces-J-bin.2.11.0.tar.gz の中にある、xercesImpl.jarと、xml-apis.jar をEclipseのライブラリに追加します。
Download Xerces-J-bin.2.11.0.tar.gz. In this file, you can get xercesImpl.jar and xml-apis.jar. Then you need to add these Jar-files into Eclipse-library.


(4)コーディング Coding
こちらのサイトを参考にしました。
This site will help you.
JavaのRSSライブラリ、ROMEを使ってみる
RSS・Atomフィードのためのライブラリ”ROME” – argius note
Kazuhiro’s Weblog: Javaで手軽にフィード(RSS、Atom)を扱うライブラリ 「ROME」

RSS取得時のコードは以下のような書き方になります。
You can get RSS with this code.

        String url = "http://jp.techcrunch.com/feed/";

        URL feedUrl = new URL(url);
        XmlReader reader = new XmlReader(feedUrl.openStream());
            SyndFeedInput input = new SyndFeedInput();
            SyndFeed feed = input.build(reader);
        reader.close();

上記では取得したデータが、feedの中に格納されています。
feed.getTitle()といった形で、個々のデータが取得できます。
RSSの1つ1つの記事は、feed.getEntries()でまとめて取得できます。List形式のデータになっています。

That code will give you RSS-data in  a variable “feed”.
You can get each a RSS-element using the method “feed.getTitle()”.
And you can get a RSS-entry using the method “feed.getEntries()”.

とりあえず、取得したデータをすべて表示してみることにしました。最終的なソースコードを以下にまとめて記載します。
I could show you each elements in RSS., If you use this code.

RomeSample3.java

import java.net.URL;
import java.util.List;

import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.io.SyndFeedInput;
import com.sun.syndication.io.XmlReader;

public class RomeSample3 {
    public static void main(String[] args) throws Exception {
        // String url = "http://rss.rssad.jp/rss/gihyo/feed/rss2";

        // String url = "http://rss.dailynews.yahoo.co.jp/fc/rss.xml";
        String url = "http://jp.techcrunch.com/feed/";
        // String url = "http://wired.jp/rssfeeder/";

        URL feedUrl = new URL(url);
        XmlReader reader = new XmlReader(feedUrl.openStream());
            SyndFeedInput input = new SyndFeedInput();
            SyndFeed feed = input.build(reader);
        reader.close();

        // ###########################################################
        System.out.println("----- Site Infomation ----------------------------------");
        System.out.println("SITE author : " + feed.getAuthor());
        System.out.println("SITE authors : " + feed.getAuthors());
        System.out.println("SITE categories : " + feed.getCategories());
        System.out.println("SITE contributors : " + feed.getContributors());
        System.out.println("SITE Copyright : " + feed.getCopyright());
        System.out.println("SITE description : " + feed.getDescription());
        System.out.println("SITE DescriptionEx : " + feed.getDescriptionEx());
        System.out.println("SITE encoding : " + feed.getEncoding());
        System.out.println("SITE feedType : " + feed.getFeedType());
        System.out.println("SITE ForeignMarkup : " + feed.getForeignMarkup());
        System.out.println("SITE image : " + feed.getImage());
        System.out.println("SITE language : " + feed.getLanguage());
        System.out.println("SITE link : " + feed.getLink());
        System.out.println("SITE links : " + feed.getLinks());
        // System.out.println("SITE Module : " + feed.getModule(url));
        // System.out.println("SITE Modules : " + feed.getModules());
        System.out.println("SITE getPublishedDate : " + feed.getPublishedDate());
        System.out.println("SITE getSupportedFeedTypes : " + feed.getSupportedFeedTypes());
        System.out.println("SITE title : " + feed.getTitle());
        System.out.println("SITE getTitleEx : " + feed.getTitleEx());
        System.out.println("SITE uri : " + feed.getUri());

        // ###########################################################
        List<SyndEntry> entries = feed.getEntries();

        Integer i = 1;
        for (SyndEntry entry : entries) {
            System.out.println("----- Entries No." + i + "----------------------------------");
            System.out.println("ENTRY Author : " + entry.getAuthor());
            System.out.println("ENTRY Authors : " + entry.getAuthors());
            System.out.println("ENTRY Categories : " + entry.getCategories());
            System.out.println("ENTRY Contents : " + entry.getContents());
            System.out.println("ENTRY Contributors : " + entry.getContributors());
            System.out.println("ENTRY Description : " + entry.getDescription());
            System.out.println("ENTRY Enclosures : " + entry.getEnclosures());
            System.out.println("ENTRY ForeignMarkup : " + entry.getForeignMarkup());
            System.out.println("ENTRY Link : " + entry.getLink());
            System.out.println("ENTRY Links : " + entry.getLinks());
            // System.out.println("ENTRY Module : " + entry.getModule(url));
            // System.out.println("ENTRY Modules : " + entry.getModules());
            System.out.println("ENTRY PublishedDate : " + entry.getPublishedDate());
            System.out.println("ENTRY Source : " + entry.getSource());
            System.out.println("ENTRY Title : " + entry.getTitle());
            System.out.println("ENTRY TitleEx : " + entry.getTitleEx());
            System.out.println("ENTRY UpdatedDate : " + entry.getUpdatedDate());
            System.out.println("ENTRY Uri : " + entry.getUri());
            System.out.println("ENTRY WireEntry : " + entry.getWireEntry());
            System.out.println("");

            i = i + 1;
        }
    }
}

これで、RSSの受信はできました。I could get RSS.

ほしい情報は、記事のタイトルと要約の部分になります。ソース上では以下の部分。
Title and Descriptin are important for me. I can get those using this method .

entry.getTitle()
entry.getDescription()

実際にRSS取得してみると、記事丸ごとが広告になっている場合と、要約の中に広告が埋まっている場合があります。
タイトルが”PR:”で始まっているものは、記事全体が広告になってる。これはタイトルの文字列をチェックするだけなので取り除くのは簡単です。
要約の中に広告が埋まっている場合は、以下のような形で、広告のリンクが埋まっています。

I got RSS-feed, then I found that there is some Ads in RSS. Some entry are ads, and some RSS-description has ads.
If “Title” start with “PR:” , RSS-entry is a advertisement. It’s easy to check “Title”.
But if RSS-description had ads, it’s difficult to remove ads.
This is sample that there are “link to ads” in description.

<p>前回紹介したように,gmtpに文字コード回りのパッチをあてることで,無事Plamo Linux環境とNexus 7の間でファイルをやりとりできるようになりました。しかしながら,あれこれ使っているとgmtpの欠点も目についてきました。
<img border="0" width="1" height="1" src="http://rss.rssad.jp/rss/artimg/47ghQF72z411/3184e1aa6c761b751f68c2613f8a3f55"/></p>
<br clear="all" /><a href="http://rss.rssad.jp/rss/ad/47ghQF72z411/pFjyc8QFo.Eh?type=1" target="_blank"><img src="http://rss.rssad.jp/rss/img/47ghQF72z411/pFjyc8QFo.Eh?type=1" border="0"/></a><br/>

この場合HTMLをParseする必要があるため、HTMLパーサのライブラリを使い、不要な部分のみを取り除くことにします。ここから先は、次回のエントリーで。
In this case, I have to parse HTML. So I will use HTML-parsing-library. And I will remove the link of ads.
I will show you that in next post.

GAEでSpringのValidation機能を使おうとしてハマった

GAE上でSpringMVCを動かしています。formからの入力にValidationを試してみました。で、どっぷりハマったので、とりあえず原因を速報。

“hibernate-validator 5.*” don’t work on GAE!!

————————————————————–

使っている環境

Java JDK1.6.0_35(64bit版)
Google App Engine SDK for Java 1.7.1
Eclipse IDE for Java EE Developers_ Juno (4.2)64Bit版
The Google Plugin for Eclipse, for Eclipse 3.8/4.2 (Juno)
Spring Framework 3.2.2.RELEASE
Commons Collections 3.2.1
Commons Lang 2.6
Commons Logging 1.1.2
Apache Velocity 1.7
hibernate-validator-4.3.0.Final ( + jboss-logging-3.1.0.CR2.jar, validation-api-1.0.0.GA.jar)


(1)NG編

まず、現時点での最新版である、
hibernate-validator 5.0.0.Final
を使ってみました。I try to use latest version “hibernate-validator 5.0.0.Final”.

ダウンロード先はこちら。
Downloads – JBoss Community

ダウンロードしたhibernate-validator-5.0.0.Final-dist.tar.gzの中にある、
hibernate-validator-5.0.0.Final.jar
validation-api-1.1.0.Final.jar
jboss-logging-3.1.1.GA.jar

をライブラリに追加して、アプリ起動、以下のエラーが出ました。NoClassFoundError has occured.

java.lang.NoClassDefFoundError: com/fasterxml/classmate/Filter

こちらのサイトから
SpringSource Enterprise Bundle Repository

FasterXML ClassMate 0.5.4をダウンロードして、com.springsource.com.fasterxml.classmate-0.5.4.jarをライブラリに追加しました。
アプリ起動してみると、またエラーが出ました。NoClassFoundError has occured again !

java.lang.ClassNotFoundException: de.odysseus.el.ExpressionFactoryImpl

そのため、今度はこちらのサイトから、
Java Unified Expression Language

juel-2.2.6をダウンロードしてきて、juel-impl-2.2.6.jarをライブラリに追加しました。
そうすると、アプリが正常に起動するようになりました。

しかし!

実際にValidation機能を作動させると、INTERNAL_SERVER_ERRORが発生。
このような謎のエラーが出てました。InternalServerError has occured !!

java.lang.ExceptionInInitializerError
    at org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator.interpolateExpression(ResourceBundleMessageInterpolator.java:227)
    at org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator.interpolateMessage(ResourceBundleMessageInterpolator.java:187)
    at org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator.interpolate(ResourceBundleMessageInterpolator.java:120)
   :   :
   :   :

Caused by: javax.el.ELException: Could not create expression factory instance
    at javax.el.ExpressionFactory.newInstance(ExpressionFactory.java:225)
    at javax.el.ExpressionFactory.newInstance(ExpressionFactory.java:183)
    at javax.el.ExpressionFactory.newInstance(ExpressionFactory.java:89)
    at org.hibernate.validator.internal.engine.messageinterpolation.InterpolationTerm.<clinit>(InterpolationTerm.java:60)
    ... 68 more
Caused by: java.security.AccessControlException: access denied (java.io.FilePermission C:\Program Files\Java\jdk1.6.0_35\jre\lib\el.properties read)
    at java.security.AccessControlContext.checkPermission(AccessControlContext.java:374)
    at java.security.AccessController.checkPermission(AccessController.java:546)
    at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
    at com.google.appengine.tools.development.DevAppServerFactory$CustomSecurityManager.checkPermission(DevAppServerFactory.java:283)
    at java.lang.SecurityManager.checkRead(SecurityManager.java:871)
    at java.io.File.exists(File.java:731)
    at de.odysseus.el.ExpressionFactoryImpl.loadDefaultProperties(ExpressionFactoryImpl.java:258)
    at de.odysseus.el.ExpressionFactoryImpl.loadProperties(ExpressionFactoryImpl.java:283)
    at de.odysseus.el.ExpressionFactoryImpl.<init>(ExpressionFactoryImpl.java:162)
    at de.odysseus.el.ExpressionFactoryImpl.<init>(ExpressionFactoryImpl.java:147)
   :   :
   :   :

el.propertiesとかいうファイルのことをいろいろ調べたものの、情報少なく、原因不明。

おそらく、de.odysseus.el.ExpressionFactoryImplがファイルを読み込もうとしている。で、GAEではファイルの読み書きは禁止されているのでエラーとなっている。
I think “juel-2.2.6” try to read el.properties. But, on GAE, it doesn’t permit to read files. So Error has occured. I don’t know Why “juel” try to read.

しかし、SpringのTutorialをみると、きちんと動いている!
But , according to “SpringTutorial”,  It’s work well !
Spring Security in Google App Engine | SpringSource Team Blog


(2)Success!!
最終的に分かったのは、どうもhibernate-validator-5.*からちょっとつくりが変わっているみたい。
I think that  hibernate-validator-5.* has changed any architecture.

hibernate-validator-4.3.0.Finalを使ったらうまく動くようになりました。
I try to use “hibernate-validator-4.3.0.Final”, it’s work well !!
Don’t use “hibernate-validator-5.*” on GAE !!  Use “hibernate-validator-4.3.0″

また、hibernate-validator-4.3.0を使った場合、FasterXML、juelは無くてもOKです。
You don’t need “FasterXML and juel”, when you use “hibernate-validator-4.3.0” on GAE.