5 import java.util.ArrayList;
\r
6 import java.util.Calendar;
\r
7 import java.util.Date;
\r
8 import java.util.GregorianCalendar;
\r
9 import java.util.HashMap;
\r
10 import java.util.LinkedHashMap;
\r
11 import java.util.regex.Matcher;
\r
12 import java.util.regex.Pattern;
\r
15 public class PlugIn_TVPMSN extends TVProgramUtils implements TVProgram,Cloneable {
\r
17 private static final String thisEncoding = "UTF-8";
\r
23 public String getTVProgramId() { return "MSNエンタメ"; }
\r
26 public ProgType getType() { return ProgType.PROG; }
\r
28 public ProgSubtype getSubtype() { return ProgSubtype.TERRA; }
\r
32 public PlugIn_TVPMSN clone() {
\r
33 return (PlugIn_TVPMSN) super.clone();
\r
39 public int getTimeBarStart() {return 5;}
\r
41 private int getDogDays() { return ((getExpandTo8())?(8):(7)); }
\r
43 private static final int bscntMax = 10;
\r
44 private int bscnt = 4;
\r
45 public void setBscnt(int n) { bscnt = n; }
\r
47 private String getBscntFile() { return String.format("env"+File.separator+"bscnt.%s",getTVProgramId()); }
\r
49 /*******************************************************************************
\r
51 ******************************************************************************/
\r
53 private final String MSGID = "["+getTVProgramId()+"] ";
\r
54 private final String ERRID = "[ERROR]"+MSGID;
\r
55 private final String DBGID = "[DEBUG]"+MSGID;
\r
58 private ArrayList<ProgList> newplist = null;
\r
60 private HashMap<String,String> nf = null;
\r
64 public void loadProgram(String areaCode, boolean force) {
\r
67 newplist = new ArrayList<ProgList>();
\r
69 nf = new HashMap<String, String>();
\r
71 // 地域コードごとの参照ページ数の入れ物を用意する
\r
72 LinkedHashMap<String,Integer> pages = new LinkedHashMap<String, Integer>();
\r
75 if ( areaCode.equals(allCode) ) {
\r
77 for ( Center cr : crlist ) {
\r
78 if ( cr.getOrder() > 0 ) {
\r
80 pages.put(cr.getAreaCode(),0);
\r
86 pages.put(areaCode,0);
\r
87 pages.put(bsCode,0);
\r
90 // トップの下に局ごとのリストを生やす
\r
91 for ( String ac : pages.keySet() ) {
\r
92 for ( Center cr : crlist ) {
\r
93 if ( ac.equals(cr.getAreaCode()) ) {
\r
94 ProgList pl = new ProgList();
\r
95 pl.Area = cr.getAreaCode();
\r
96 pl.SubArea = cr.getType();
\r
97 pl.Center = cr.getCenter();
\r
98 pl.BgColor = cr.getBgColor();
\r
100 // <TABLE>タグの列数を決め打ちで処理するので、設定上無効な局も内部的には列の1つとして必要
\r
101 pl.enabled = (cr.getOrder()>0)?(true):(false);
\r
105 int pg = Integer.valueOf(cr.getType());
\r
106 if ( pl.enabled && pages.get(ac) < pg ) {
\r
107 // 地域コードごとの最大参照ページ数を格納する
\r
114 // 局の下に日付ごとのリストを生やす
\r
115 GregorianCalendar cal = new GregorianCalendar();
\r
116 cal.setTime(new Date());
\r
117 if ( CommonUtils.isLateNight(cal) ) {
\r
119 cal.add(Calendar.DATE, -1);
\r
121 GregorianCalendar cale = (GregorianCalendar) cal.clone();
\r
122 for (int i=0; i<getDogDays(); i++) {
\r
123 String date = CommonUtils.getDate(cale);
\r
124 for ( ProgList pl : newplist ) {
\r
125 ProgDateList cl = new ProgDateList();
\r
129 cale.add(Calendar.DATE, 1);
\r
133 int counterMax = 0;
\r
134 for ( String ac : pages.keySet() ) {
\r
135 counterMax += pages.get(ac)*getDogDays();
\r
138 // 日付の下に番組情報ごとのリストを生やす(MSNは1ページに複数局存在する)
\r
140 for ( String ac : pages.keySet() ) {
\r
141 cale = (GregorianCalendar) cal.clone();
\r
142 for ( int i=0; i<getDogDays(); i++ ) {
\r
143 String date = CommonUtils.getDateYMD(cale);
\r
144 for ( int d=1; d<=pages.get(ac) && d<=REFPAGESMAX; d++ ) { // 最大{REFPAGESMAX}ページまでしか参照しない
\r
146 if ( ac.equals(bsCode) ) {
\r
147 url = "http://program.tv.jp.msn.com/tv.php?site=032&mode=06&category=d"+d+"&area="+ac+"&template=program&sdate="+date+"&shour=05&lhour=24";
\r
151 url = "http://program.tv.jp.msn.com/tv.php?site=032&mode=06&category=g&area="+ac+"&template=program&sdate="+date+"&shour=05&lhour=24";
\r
154 url = "http://program.tv.jp.msn.com/tv.php?site=032&mode=06&category=s&area="+ac+"&template=program&sdate="+date+"&shour=05&lhour=24";
\r
157 _loadProgram(ac, String.valueOf(d), url, force, i, cale.get(Calendar.MONTH)+1, cale.get(Calendar.DATE), counter++, counterMax);
\r
160 cale.add(Calendar.DATE, 1);
\r
164 // 開始・終了日時を正しい値に計算しなおす
\r
165 for (ProgList pl : newplist) {
\r
166 setAccurateDate(pl.pdate);
\r
171 for ( String f : nf.keySet() ) {
\r
172 System.err.println(String.format("【デバッグ情報】未定義のフラグは[?]と表示されます。: [%s]",f));
\r
177 pcenter = newplist;
\r
188 private void _loadProgram(String areacode, String page, String url, boolean force, int wdaycol, int month, int day, int counter, int counterMax) {
\r
191 final String progCacheFile = String.format(getProgDir()+File.separator+"TVMSN_%s_%s_%04d.html", areacode, page, day);
\r
193 File f = new File(progCacheFile);
\r
194 if (force == true ||
\r
195 (f.exists() == true && isCacheOld(progCacheFile) == true) ||
\r
196 (f.exists() == false && isCacheOld(null) == true)) {
\r
197 webToFile(url, progCacheFile, thisEncoding);
\r
198 reportProgress(String.format("%s(%s)を取得しました[%s-%02d日-%sページ]: (%d/%d) %s",getTVProgramId(),"オンライン",getArea(areacode),day,page,counter,counterMax,url));
\r
200 else if (CommonUtils.isFileAvailable(f,10)) {
\r
201 reportProgress(String.format("%s(%s)を取得しました[%s-%02d日-%sページ]: (%d/%d) %s",getTVProgramId(),"キャッシュ",getArea(areacode),day,page,counter,counterMax,url));
\r
204 reportProgress(String.format("%s(%s)がみつかりません[%s-%02d日-%sページ]: (%d/%d) %s",getTVProgramId(),"キャッシュ",getArea(areacode),day,page,counter,counterMax,url));
\r
209 String response = CommonUtils.read4file(progCacheFile, false);
\r
211 // キャッシュが不整合を起こしていたら投げ捨てる
\r
212 Matcher ma = Pattern.compile(String.format("<h1 class=\"headtext\">%d月%d日",month,day)).matcher(response);
\r
213 if ( ! ma.find() ) {
\r
214 reportProgress(getTVProgramId()+"(キャッシュ)が無効です: ("+counter+"/"+counterMax+") "+progCacheFile);
\r
219 getPrograms(areacode, page, wdaycol, response);
\r
221 catch (Exception e) {
\r
228 private void getPrograms(String areacode, String page, int wdaycol, String src) {
\r
229 Matcher ma = Pattern.compile("\n(<TD ROWSPAN =(\\d+) CLASS = \".*?\"> </TD>|<td width=\".*?\" valign=\".*?\" id=\".*?\" ROWSPAN =.*?</A></DIV></TD>)").matcher(src);
\r
230 while (ma.find()) {
\r
234 ProgDetailList pdl = new ProgDetailList();
\r
236 if (ma.group(1).startsWith("<TD")) {
\r
237 //System.err.println(ma.group(1));
\r
238 pdl.title = "番組情報がありません";
\r
239 pdl.length = Integer.valueOf(ma.group(2));
\r
246 // 5,6 : start-hour,min
\r
247 // 7,8 : end-hour,min
\r
248 // 9,10 : detail1,detail2
\r
249 Matcher mb = Pattern.compile("<td width=\".*?\" valign=\".*?\" id=\".*?\" ROWSPAN =(\\d+) CLASS = \"(.*?)\"><DIV [^>]*?><a href=.*?onClick=\"xClickACT\\(\'\\.(.+?)\'\\);.*?<h1>(.*?)</h1><h2>.*?(\\d\\d):(\\d\\d)~(\\d\\d):(\\d\\d).*?</h2><p>(.*?)</p><p>(.*?)</p>.*?</A></DIV></TD>").matcher(ma.group(1));
\r
250 if ( ! mb.find() ) {
\r
251 System.err.println("TVMSN: unexpected string= "+ma.group(1));
\r
257 pdl.title = replaceMarks(pdl, CommonUtils.unEscape(mb.group(4)));
\r
258 pdl.splitted_title = pdl.title;
\r
264 if (mb.group(9).length() > 0) {
\r
265 pdl.detail = CommonUtils.unEscape(String.format("%s\n%s", mb.group(9),mb.group(10)));
\r
268 pdl.detail = CommonUtils.unEscape(mb.group(10));
\r
271 pdl.detail = replaceMarks(pdl, pdl.detail);
\r
274 pdl.detail = pdl.detail.replaceAll("<a[^>]*?>", "");
\r
275 pdl.detail = pdl.detail.replaceAll("</a>", "");
\r
277 pdl.splitted_detail = pdl.detail;
\r
281 pdl.titlePop = TraceProgram.replacePop(pdl.title);
\r
282 pdl.detailPop = TraceProgram.replacePop(pdl.detail);
\r
283 pdl.SearchStrKeys = TraceProgram.splitKeys(pdl.titlePop);
\r
286 pdl.link = "http://program.tv.jp.msn.com"+mb.group(3);
\r
289 pdl.length = Integer.valueOf(mb.group(1));
\r
293 if (mb.group(2).equals(allCode)) {
\r
294 pdl.genre = ProgGenre.NOGENRE;
\r
296 else if (mb.group(2).equals("anime")) {
\r
297 pdl.genre = ProgGenre.ANIME;
\r
299 else if (mb.group(2).equals("movie")) {
\r
300 pdl.genre = ProgGenre.MOVIE;
\r
302 else if (mb.group(2).equals("dorama")) {
\r
303 pdl.genre = ProgGenre.DORAMA;
\r
305 else if (mb.group(2).equals("sports")) {
\r
306 pdl.genre = ProgGenre.SPORTS;
\r
309 pdl.genre = ProgGenre.NOGENRE;
\r
313 GregorianCalendar c = new GregorianCalendar();
\r
314 c.setTime(new Date());
\r
315 c.set(Calendar.HOUR_OF_DAY, Integer.valueOf(mb.group(5)));
\r
316 c.set(Calendar.MINUTE, Integer.valueOf(mb.group(6)));
\r
317 pdl.start = String.format("%02d:%02d", c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE));
\r
319 c.set(Calendar.HOUR_OF_DAY, Integer.valueOf(mb.group(7)));
\r
320 c.set(Calendar.MINUTE, Integer.valueOf(mb.group(8)));
\r
321 pdl.end = String.format("%02d:%02d", c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE));
\r
327 for ( int i=0; i<newplist.size(); i++ ) {
\r
328 ProgList pl = newplist.get(i);
\r
329 if ( ! (pl.Area.equals(areacode) && pl.SubArea.equals(page)) ) {
\r
332 if (pl.pdate.get(wdaycol).row < rowMin) {
\r
334 rowMin = pl.pdate.get(wdaycol).row;
\r
340 ProgDateList pcl = newplist.get(col).pdate.get(wdaycol);
\r
341 pcl.pdetail.add(pdl);
\r
342 pcl.row += pdl.length;
\r
346 private String replaceMarks(ProgDetailList pdl, String text) {
\r
348 HashMap<String, Integer> xf = new HashMap<String, Integer>();
\r
352 String[] marks = new String[] {
\r
357 "ppv", // [PV]ペイパービュー
\r
358 "moji", // [字]文字多重放送
\r
359 "nikakoku", // [二]二か国語放送
\r
360 "taju", // [多]音声多重放送
\r
361 "dubbed", // [吹]吹き替え
\r
366 for (int i=0; i<marks.length; i++) {
\r
367 String mark = marks[i];
\r
368 String expr = String.format("/ico_%s\\.gif>", mark);
\r
369 Matcher mx = Pattern.compile(expr, Pattern.DOTALL).matcher(text);
\r
375 for ( String mark : xf.keySet() ) {
\r
376 String expr = String.format("<img src=http://img.tv.msn.co.jp/s/ico_%s\\.gif>", mark);
\r
377 text = text.replaceAll(expr,"");
\r
378 switch (xf.get(mark)) {
\r
380 pdl.flag = ProgFlags.NEW;
\r
383 pdl.flag = ProgFlags.LAST;
\r
386 pdl.addOption(ProgOption.REPEAT);
\r
389 pdl.addOption(ProgOption.FIRST);
\r
392 pdl.addOption(ProgOption.PV);
\r
395 pdl.addOption(ProgOption.SUBTITLE);
\r
398 pdl.addOption(ProgOption.BILINGUAL);
\r
401 pdl.addOption(ProgOption.MULTIVOICE);
\r
404 pdl.addOption(ProgOption.STANDIN);
\r
407 pdl.addOption(ProgOption.DATA);
\r
415 HashMap<String,String> marks = new HashMap<String,String>();
\r
417 marks.put("director", "[監]");
\r
418 marks.put("guest", "[ゲ]");
\r
419 marks.put("jikkyo", "[実]");
\r
420 marks.put("kaisetsusya", "[解]");
\r
421 marks.put("katari", "[語]");
\r
422 marks.put("koe", "[声]");
\r
423 marks.put("org", "[原]");
\r
424 marks.put("plot", "[脚]");
\r
425 marks.put("shikai", "[司]");
\r
426 marks.put("syutsuen", "[出]");
\r
427 marks.put("n", "[N]");
\r
428 marks.put("tenki", "[天]");
\r
429 marks.put("zen", "(前編)");
\r
430 marks.put("kou", "(後編)");
\r
432 marks.put("s", "");
\r
433 marks.put("3d", "");
\r
434 marks.put("eiga", "");
\r
435 marks.put("hand", "");
\r
437 for ( String mark : marks.keySet() ) {
\r
438 String expr = String.format("<img src=http://img.tv.msn.co.jp/s/ico_%s\\.gif>", mark);
\r
439 text = text.replaceAll(expr,marks.get(mark));
\r
444 Matcher mx = Pattern.compile("/ico_(.*?)\\.gif>", Pattern.DOTALL).matcher(text);
\r
445 while ( mx.find() ) {
\r
446 nf.put(mx.group(1), null);
\r
448 for ( String mark : nf.keySet() ) {
\r
449 String expr = String.format("<img src=http://img.tv.msn.co.jp/s/ico_%s\\.gif>", mark);
\r
450 text = text.replaceAll(expr,"[?]");
\r
457 * ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
\r
458 * ★★★★★ 放送地域を取得する(TVAreaから降格)-ここから ★★★★★
\r
459 * ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
\r
468 public String getDefaultArea() {return "東京";}
\r
472 public void loadAreaCode(){
\r
474 // 設定ファイルが存在していればファイルから
\r
475 File f = new File(getAreaSelectedFile());
\r
476 if ( f.exists() ) {
\r
477 @SuppressWarnings("unchecked")
\r
478 ArrayList<AreaCode> tmp = (ArrayList<AreaCode>) CommonUtils.readXML(getAreaSelectedFile());
\r
479 if ( tmp != null ) {
\r
480 System.out.println("地域リストを読み込みました: "+getAreaSelectedFile());
\r
485 System.out.println("地域リストの読み込みに失敗しました: "+getAreaSelectedFile());
\r
489 ArrayList<AreaCode> newaclist = new ArrayList<AreaCode>();
\r
492 String response = "";
\r
494 String uri = "http://program.tv.jp.msn.com/tv.php?site=032&mode=06&template=program&category=g&shour=05&lhour=24";
\r
495 response = webToBuffer(uri, thisEncoding, true);
\r
496 if ( response == null ) {
\r
497 System.out.println("地域情報の取得に失敗しました: "+uri);
\r
502 Matcher ma = Pattern.compile("<select name=\"area\" size=\"1\">(.+?)</select>").matcher(response);
\r
504 Matcher mb = Pattern.compile("<option value=\"([^\"]+?)\" ?(selected=\"selected\")?>(.+?)</option>").matcher(ma.group(1));
\r
505 while (mb.find()) {
\r
506 AreaCode ac = new AreaCode();
\r
507 ac.setArea(mb.group(3));
\r
508 ac.setCode(mb.group(1));
\r
513 if ( newaclist.size() == 0 ) {
\r
514 System.err.println(ERRID+"地域一覧の取得結果が0件だったため情報を更新しません");
\r
520 AreaCode ac = new AreaCode();
\r
522 ac.setCode(allCode);
\r
523 newaclist.add(0,ac);
\r
526 AreaCode ac = new AreaCode();
\r
528 ac.setCode(bsCode);
\r
533 aclist = newaclist;
\r
538 * ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
\r
539 * ★★★★★ 放送地域を取得する(TVAreaから降格)-ここまで ★★★★★
\r
540 * ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
\r
546 * ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
\r
547 * ★★★★★ 放送局を選択する(TVCenterから降格)-ここから ★★★★★
\r
548 * ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
\r
555 // 設定ファイルがなければWebから取得
\r
557 public void loadCenter(String code, boolean force) {
\r
559 if ( code == null ) {
\r
560 System.out.println(ERRID+"地域コードがnullです.");
\r
564 // BSのページ数の初期化(事前に判明していない場合は2)
\r
565 int bscntTmp = CommonUtils.loadCnt(getBscntFile());
\r
566 bscntTmp = bscnt = (bscntTmp > 0)?(bscntTmp):(3);
\r
569 String centerListFile = getCenterListFile(getTVProgramId(), code);
\r
572 File f = new File(centerListFile);
\r
576 File f = new File(centerListFile);
\r
577 if (f.exists() == true) {
\r
578 @SuppressWarnings("unchecked")
\r
579 ArrayList<Center> tmp = (ArrayList<Center>) CommonUtils.readXML(centerListFile);
\r
580 if ( tmp != null ) {
\r
587 System.out.println("放送局リストを読み込みました: "+centerListFile);
\r
591 System.out.println("放送局リストの読み込みに失敗しました: "+centerListFile);
\r
595 ArrayList<Center> newcrlist = new ArrayList<Center>();
\r
599 int cntMax = ((allCode.equals(code))?(aclist.size()-2):(1))*2 + bscnt;
\r
601 for (AreaCode ac : aclist) {
\r
602 if (ac.getCode().equals(bsCode)) {
\r
605 else if (code.equals(allCode) && ac.getCode().equals(allCode)) {
\r
608 else if ( ! code.equals(allCode) && ! ac.getCode().equals(code)) {
\r
615 url = "http://program.tv.jp.msn.com/tv.php?site=032&mode=06&template=program&category=g&area="+ac.getCode()+"&shour=05&lhour=24";
\r
616 if ( _loadCenter(newcrlist, ac.getCode(), "1", url) ) {
\r
617 reportProgress(String.format("放送局情報を取得しました[%s%d]: (%d/%d) %s","地上波",cnt,cnt,cntMax,url));
\r
622 url = "http://program.tv.jp.msn.com/tv.php?site=032&mode=06&template=program&category=s&area="+ac.getCode()+"&shour=05&lhour=24";
\r
623 if ( _loadCenter(newcrlist, ac.getCode(), "2", url) ) {
\r
624 reportProgress(String.format("放送局情報を取得しました[%s%d]: (%d/%d) %s","地上波",cnt,cnt,cntMax,url));
\r
629 // BS1・BS2は共通にする(bscntは_loadCenter()中で増加する可能性あり)
\r
631 for ( int d=1; d<=bscnt; d++ )
\r
633 String url = "http://program.tv.jp.msn.com/tv.php?site=032&mode=06&template=program&category=d"+d+"&shour=05&lhour=24";
\r
634 if ( _loadCenter(newcrlist, bsCode, String.valueOf(d), url) ) {
\r
635 reportProgress(String.format("放送局情報を取得しました[%s%d]: (%d/%d) %s","BS",d,cnt,cntMax,url));
\r
642 if ( bscntTmp < bscnt ) {
\r
643 reportProgress(String.format("BSのページ数が変更されました: %d→%d (最大%dまで)",bscntTmp,bscnt,bscntMax));
\r
644 CommonUtils.saveCnt(bscnt,getBscntFile());
\r
647 if ( newcrlist.size() == 0 ) {
\r
648 System.err.println(ERRID+"放送局情報の取得結果が0件だったため情報を更新しません");
\r
652 crlist = newcrlist;
\r
653 attachChFilters(); // 放送局名変換
\r
657 private boolean _loadCenter(ArrayList<Center> newcrlist, String code, String page, String uri) {
\r
658 String response = null;
\r
660 response = webToBuffer(uri, thisEncoding, true);
\r
661 if ( response == null ) {
\r
662 System.out.println("放送局情報の取得に失敗しました: "+uri);
\r
669 for ( int i=bscnt+1; i<=bscntMax; i++ ) {
\r
670 if ( ! response.matches(".*&category=d"+i+"\".*") ) {
\r
671 if ( bscnt < i-1 ) {
\r
680 Matcher ma = Pattern.compile("<!-- ↓ Station -->([\\s\\S]+?)<!-- ↑ Station -->").matcher(response);
\r
682 Matcher mb = Pattern.compile("<TH width=\".*?\">\\s*(.+?)\\s*</th>").matcher(ma.group(1));
\r
683 while (mb.find()) {
\r
686 Matcher mc = Pattern.compile("<a href =\"(.*?)\" id=\'ch01_h\' class=\"th_a\" target=\"_blank\"\\s*>\\s*(.+?)(<BR>|</a>)").matcher(mb.group(1));
\r
688 centerName = CommonUtils.unEscape(mc.group(2));
\r
689 link = mb.group(1);
\r
692 centerName = CommonUtils.unEscape(mb.group(1).replaceAll("<BR>", ""));
\r
697 centerName = centerName.replaceFirst("^NHK$", "NHK総合");
\r
698 centerName = centerName.replaceFirst("^NHK Eテレ", "NHK Eテレ");
\r
699 if ( ! code.startsWith(bsCode) && page.equals("1")) {
\r
700 if (centerName.startsWith("NHK")) {
\r
701 centerName = centerName+"・"+getArea(code);
\r
705 Center cr = new Center();
\r
706 cr.setAreaCode(code);
\r
707 cr.setCenterOrig(centerName);
\r
710 cr.setEnabled(true);
\r
718 * ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
\r
719 * ★★★★★ 放送局を選択する(TVCenterから降格)-ここまで ★★★★★
\r
720 * ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
\r