:::

「國圖帶頭侵害著作權,變造名目收費」個案研究

布丁布丁吃布丁

「國圖帶頭侵害著作權,變造名目收費」個案研究

※感謝背後默默地推手李同學,讓我們可以完成這篇報告

背景說明

新新聞1118期封面報導「國圖帶頭侵害著作權,變造名目收費」中內文提及:[1]

「國圖館疑以公帑圖利特定廠商 全國各出版社提供4000種以上期刊雜誌給國圖館,國圖館以數位典藏名義掃描後,進一步開發遠距圖書服務系統,獨家委託凌網公司以商業型態銷售儲值點數,販賣所有文章。外界質疑凌網公司在沒有付出任何代價情況下,就輕易取得經營權,又在分配收益的比例上取得95%的大餅,內幕的確啟人疑竇。」

該篇報導指稱國家圖書館獨家委託凌網開發的「遠距圖書服務系統」販售未授權文章,並質疑國家圖書館與凌網公司關係匪淺,可能有不法行為。凌網公司於8月8日已經發表新聞稿澄清該報導中未經查證的不實指控,解釋國家圖書館、工研院電通所的合作關係。[2]

因此本篇報告的重點在於探討「遠距圖書服務系統」在提供文獻傳遞服務之授權合法性,並比較中文期刊服務的營運模式,藉以提出改善的建議。

國家圖書館遠距圖書服務系統

國家圖書館遠距圖書服務系統,乃由國家圖書館依據圖書館法第八條之規定,為服務讀者「獲取公平、自由、適時及便利之圖書資訊」並合法使用,特開放「遠距圖書服務系統」,以提供使用者即時查詢各類出版資訊或獲取文獻。

遠距圖書服務系統提供下列七種資料庫之出版資訊:「中文期刊篇目索引影像系統」、「中華民國出版期刊指南系統」、「政府公報查詢系統」、「政府統計查詢系統」、「中華民國政府出版品目錄查詢系統」、「公務出國報告查詢系統」、「當代文學史料系統」等資料庫,以及其他相似性質之資料庫或其他服務內容。依照國家圖書館或著作權人授權內容暨著作權法關於合理使用之規定,得以郵寄、傳真、螢幕顯示或線上列印四種方式,傳遞文獻予讀者,並由讀者自行負擔取得文獻之費用。[3]

使用授權與收費

遠距圖書服務系統僅有在文獻傳遞時收費,書目查詢均免費使用,其中「公務出國報告查詢系統」之文獻傳遞也不收費。依照收費標準,文獻傳遞系統工本費每頁不得超過4元;郵資費用依臺灣郵政郵件資費訂定,郵寄人工處理費每件不得超過30元;傳真通訊費用依中華電信資費表訂定。而此價目表中未明定之收費項目與金額,是由受委託之營運單位陳報國家圖書館同意後,始得收取。

遠距圖書服務系統中,僅有無償或有償授權的資料才能提供線上顯示或直接列印原文內容。若沒有授權,為避免觸犯「公開傳播權」,則只能由服務中心傳真或郵寄給讀者。並且依照著作權法第65條之規定,「著作之利用…..,應審酌一切情狀,尤應注意左列事項,以為判斷之標準:…. 三、所利用之質量及其在整個 著作所占之比例。 四、利用結果對著作潛在市場與現在價值之影響」凡近六個月內出版且未獲得著作權人授權之期刊資料,該系統目前暫不提供複印本文之服務。[4]

國家圖書館遠距圖書服務收費項目中皆需系統工本費,單次計費為每頁3元,會員則是每頁2元,再依文獻傳遞方式之不同,酌收取通訊、人工處理或郵資所需之費用,其服務費率如表格1。

表格 1 國家圖書館遠距圖書服務系統 服務費率[5]

文獻傳遞方式

費用 (依每份文獻計費)

線上顯示及列印
(待掃描後線上列印)

  • 無償授權:系統工本費
  • 有償授權:系統工本費+著作權利金

傳真

  • 系統工本費 + 通訊費用
  • 通訊費用
    台灣:
    .台北地區:一頁一元
    .台北以外地區:一頁三元
    國外地區:一頁三十元

郵寄

  • 系統工本費
  • 人工處理費:每篇二十元
  • 郵資費用 :依每件文獻的頁數換算成重量,計算郵資費用

尚未掃描文獻

可提出申請,等待三個工作天的時間處理,掃描完成後再依讀者申請傳遞方式處理。

備註

系統工本費費率
單次計費:每頁3元;儲值會員:每頁優惠為2元

國家圖書館遠距圖書服務系統皆是採無償授權,個人使用者可下載授權書自行列印、填寫,並親筆簽名蓋印後寄至國家圖書館完成授權。如果個人作者授權於遠距圖書服務系統,將可收到免費閱覽、列印作者授權之文獻、查詢作者授權文獻之使用統計、獲得遠距服務體驗點數100點(期限一年)專屬回饋服務。而資料庫業者與仲介團體也可跟遠距圖書服務系統合作,共享系統機制、降低建置成本,也可由業者自由訂價,增加額外營收。詳細的合作方案則需跟凌網科技洽詢。[6]

中文電子期刊服務CEPS

中文電子期刊服務(Chinese Electronic Periodical Services, CEPS」資料庫由華藝公司所建置,於2004年7月開始營運。是國內第一家擁有合法著作權連線下載的「中文電子期刊服務」,資料庫中收錄年代自西元1991年起,主要為2000年之後,包括約3,529種台灣及中國大陸以中文為主要語言的期刊,其中2,668種為大陸出版之期刊(至2008年9月30為止之數據統計)。台灣期刊以學術期刊、指標期刊(國科會獎助、TSSCI等)為主要收錄範圍;大陸期刊以指標期刊(EI, SCI-E, SSCI,CSSCI, MEDLINE,中國科技指引等指標料庫收錄者)為收錄標準。電子全文截至2008 年8月已超過一百二十六萬篇,每月持續新增四萬篇以上電子全文,全文率達90%-95%。學科類別包含人文學、社會科學、自然科學、應用科學和醫學與生命科學五大類別。[7]

使用授權與收費

在文獻傳遞方式上,CEPS僅提供電子全文下載服務。而在CEPS的收費制度是以文章頁數為扣點基準,每1點為新台幣1元。CEPS的授權分成有償授權跟無償授權,特色如下:[8]

  • 有償授權:全文收費知識有價,合理付費使用
    • 資料庫營收分配給出版單位與作者,形成良性價值分配鏈。
    • 編輯部可將版稅轉換成相對應價值,使用其他著作權人貢獻的學術資源
    • 全球讀者可接受模式選項之一
  • 無償授權:全文免費
    • 全球讀者無須付費,即可免費瀏覽電子全文
    • 編輯部與作者無版稅分享
    • 編輯部共享其他同樣免費無償授權的全文,但須付費使用有償授權資源
    • 全球讀者可接受模式選項之一

華藝公司與期刊出版單位或授權代理商簽署非專屬授權,將資料庫營收分配給出版單位,再由出版單位分配給作者。而非專屬授權使得文獻可授權於中文期刊服務並同時在其他地方刊載出版,其關係如圖1。[9]

image

圖 1 CEPS結算分配

國家圖書館遠距圖書服務系統與中文電子期刊服務之比較

現今國內以中文期刊為主的電子資料庫服務,除了國家圖書館遠距圖書服務系統之外,就是華藝公司的中文電子期刊服務。因此本報告將中文電子期刊服務作為比較對象,一一檢視兩者在營運機構、授權方式、文獻傳遞方式與收取費用之間的差異,可發現國家圖書館遠距圖書服務系統收費較高,同時文獻授權模式與傳遞模式也來得多樣化。

表格 2 國家圖書館遠距圖書服務系統與中文電子期刊服務之文獻授權與傳遞模式之比較

項目

國家圖書館

遠距圖書服務系統

中文電子期刊服務CEPS
營運機關
  • 國家圖書館提供資料庫
  • 凌網公司營運系統
華藝公司

文獻授權模式

  • 個人作者僅能無償授權,可獲得專屬回饋服務
  • 資料庫業者/仲介團體合作方案可自由訂價
  • 僅與授權代理商或出版單位合作
  • 有償授權:資料庫營收分配給出版單位與作者
  • 無償授權
文獻傳遞模式與收費
  • 郵寄:系統工本費+人工處理費每篇20元+郵資費用
  • 傳真:系統工本費+通訊費用
  • 螢幕顯示及線上列印:
    .無償授權:系統工本費
    .有償授權:系統工本費+著作權利金

※系統工本費費率,單次每頁3元、會員每頁2元

電子全文下載:每頁1元。

 

文獻傳遞授權合法性探討

國家圖書館提供遠距文獻傳遞服務,受到著作權法有關合理使用之規範無法明確界定合法提供服務之範圍,致使國家圖書館在提供遠距文獻傳遞服務時,容易與著作權人產生爭議。著作權法第四十八條規定:[10]

「供公眾使用之圖書館、博物館、歷史館、科學館、藝術館或其他文教機構,於下列情形之 一,得就其收藏之著作重製之︰

一、應閱覽人供個人研究之要求,重製已公開發表著作之一部分,或期刊或已公開發表之研討會論文集之單篇著作,每人以一份為限。……」

賴文智律師對於幾種常見的文獻傳遞模式之合法性有所探討,以下節錄與遠距圖書服務系統相關之文獻傳遞模式法規探討:[11]

「1.線上閱覽、列印

使用者透過網路對於期刊論文自國家圖書館之遠距圖書服務系統中下載著作閱覽、列印,對於使用者而言,構成重製行為,若使用者是於家中或圖書館中之機器閱覽或列印,則可引用著作權法第五十一條主張合理使用。而圖書館提供複本予讀者之行為,則可能可透過前述著作權法第四十八條第一款規定處理,但此種著作提供上之便利性(尤其系統無法控制使用者重製是否為非營利目的,以及是否為連續性重製),是否造成著作權人之損害,仍有疑義。故依現行著作權法,遠距圖書服務系統透過線上閱覽或列印方式提供著作予使用者,雖有合理使用之空間,但仍可能產生相當程度的爭議。……

3.郵寄

圖書館對於讀者所請求重製之期刊論文,於重製完成後,應使用者要求,將所重製之複本,以郵寄方式提供予使用者,由於重製行為屬於合法,而郵寄並不涉及著作權法問題,故亦屬合理使用無疑。

4.傳真

圖書館對於讀者所請求重製之期刊論文,於重製完成後,應使用者要求,將所重製之複本,以傳真傳遞予使用者時,與郵寄方式不同之點在於所重製之複本,在傳真結束後,仍然留存於圖書館。就過去影印後傳真予使用者之情形,由於所留存之複本,因為圖書館並不會另外提供予他人使用,故評價上以傳真方式提供期刊論文,仍應屬合理使用。故若利用傳真方式提供讀者所請求之期刊論文,其主要問題點將回到圖書館為數位重製,建置數位期刊論文資料庫是否屬合理使用問題,傳真本身並無問題。」

在筆者撰文當天的查詢結果得知,中文期刊篇目索引影像系統收錄「新新聞」雜誌13581篇,而大部分的文並無提供授權也無法下載,僅能使用郵寄、傳真等其他文獻傳遞方式。[12]可知該篇新聞指控新新聞遭受侵權,此為子虛烏有之誤解。

積極爭取授權、結合開放取用學刊與創用CC的力量

著作權法在線上閱覽、列印等部份界定並不明確,這將造成包括國家圖書館在內的圖書館提供文獻傳遞服務時有所限制。然而除了等待著作權法修正之外,圖書館也可以積極徵求對於電子全文閱覽、列印的授權,並規劃明確且多樣化的授權模式,可參考中文期刊服務的有償授權,設計一套讀者、出版單位、作者皆可互惠的授權機制。

在授權的設計上,建議可以結合創用CC授權條款,以保留部份著作權利的方式,加速著作的流通與再利用,促進學術界與整體社會的知識流動。[13]文獻傳遞服務容易觸犯著作權法,乃由於文獻本身並沒有開放授權。反之,如果收錄已具備授權之文獻,例如開放取用學刊[14]或創用CC授權之資源,將能解決根本上的問題。

結語

電子全文資料庫之建置提供了使用者取用全文之方便,可增加資訊之流通、減少重複系統建置之成本與分散查詢所耗費之時間人力。然而建置過程牽涉議題層面廣泛,從資料之徵集、數位化作業、授權事宜、系統建置、行銷策略、後續維護等皆非單一機構所能負擔。

張慧銖在「建置中文電子期刊全文資料庫與服務系統初探」寫道:[15]

「此類工作應由公部門執行或由私部門建置?較持平的意見是以國外的經驗,若公部門做得好便由公部門做,如NLM;若私部門做得好便由私部門做,如Ingenta。由於目前政府的政策是民間能做的便應交給民間做,政府不必插手,因此筆者認為在有競爭的情況下受益的其實是使用者,而經濟部鼓勵民間發展知識內容數位產業,應該是本計劃發展的契機。」

筆者認為,以往公私部門各作各的作法,是難以建置完善的資訊服務系統。公共部門應提供自身擁有之社會責任的資源,例如國家圖書館藉由法定送存制度徵集的文獻[16],民間公司則應提供系統建置、授權合約協調業務與行銷經營長才等服務,分工合作、各司其職,才能建造更為合理、豐富的資訊服務系統。另一方面,圖書館應推廣開放取用期刊及創用CC授權條款,將能降低授權所消耗的成本、提昇知識流動的速度,促進我國人民知識成果累積與傳播。


參考資料

[1] 李彥謀, “國圖帶頭侵害著作權,變造名目收費,” 新新聞, 八月 10, 2008, http://news.chinatimes.com/2007Cti/2007Cti-Focus/2007Cti-Focus-Content/0,4518,9708100231+0+0+150541+0,00.html#.

[2] “凌網科技-凌網科技股份有限公司 嚴正聲明,” http://www.hyweb.com.tw/ct.asp?xItem=398&ctNode=25&mp=1

[3] “國家圖書館遠距圖書服務系統 營運管理要點,” http://www.read.com.tw/web/hypage.cgi?HYPAGE=qa/profile_2008.hpg#01.

[4] “國家圖書館遠距圖書服務系統 常見問題 文獻傳遞申請,” http://www.read.com.tw/web/hypage.cgi?HYPAGE=qa/qa3.hpg.

[5] “國家圖書館遠距圖書服務系統 文獻傳遞服務,” http://www.read.com.tw/web/hypage.cgi?HYPAGE=./qa/intro5_ncl.hpg.

[6] “國家圖書館遠距圖書服務系統 授權與營運合作,” http://www.read.com.tw/web/hypage.cgi?HYPAGE=./doc/authorize.htm.

[7] “CEPS中文電子期刊服務簡介,” http://www.stpi.org.tw/fdb/ceps/index.html.

[8] 黃國禎, “學術數位傳播的全球趨勢”
(淡江大學資圖所, 十月 19, 2008), http://research.dils.tku.edu.tw/epaper/114/%E5%AD%B8%E8%A1%93%E6%95%B8%E4%BD%8D%E5%82%B3%E6%92%AD%E7%9A%84%E5%85%A8%E7%90%83%E8%B6%A8%E5%8B%A2.ppt.

[9] 鄭茗襄, “學術資源的營運:CEPS 中文電子期刊服務、CETD 中文電子學位論文服務.”

[10] “著作權法 第48條,” http://db.lawbank.com.tw/FLAW/FLAWDOC01.asp?lsid=FL011264&lno=48.

[11] 賴文智, “從著作權法談遠距文獻傳遞服務,” 國家圖書館館訊 91, no. 4: 1-6.

[12] “國家圖書館中文期刊篇目索引影像系統:「新新聞」查詢結果,” http://readopac1.ncl.edu.tw/ncl3/search_result.jsp.

[13] “創用 CC  ─  Creative Commons Taiwan | 用善意換取善意,” http://creativecommons.org.tw/.

[14] 毛慶禎, “開放近用學術文獻館藏,” 一月 15, 2005, http://www.lins.fju.edu.tw/mao/works/oa2005.htm.

[15] 張慧銖, “建置中文電子期刊全文資料庫與服務系統初探,” 圖書資訊學刊 1, no. 3 (十二月 2003): 27-40, http://www.ceps.com.tw/ec/ecjnlarticleView.aspx?atliid=556029&issueiid=39857&jnliid=3411.

[16] 國家圖書館, “送存法規,” 文字, 十二月 7, 2007, http://www.ncl.edu.tw/ct.asp?xItem=7622&ctNode=180&mp=2.

(more...)

DSpace新增input-type——XMLMetadata篇

布丁布丁吃布丁

DSpace新增input-type——XMLMetadata篇

DSpace新增input-type目錄

  1. 安裝篇
  2. TextEditor篇
  3. FileUpload篇
  4. XMLMetadata篇

雖然以XML取名,但其實這是我自己設定的格式,以div搭配class屬性來儲存資料,而不是真的XML。

概念上是樹狀結構的儲存模式,長得如下圖:

image

節點Node用來包含節點或輸入表單Input,輸入表單Input則是用來輸入值用的。他們各自的屬性如下表:

Node 節點
  • div.node-type:node [必填|不可重複]
    節點的類型,如果你要宣告一個是node,請在此輸入「node」。是input的話就輸入「input」。
  • div.node-id [選填|不可重複]
    當此節點顯示成表格(不論是可編輯的表單或顯示用的表格),該表格的id會設定這個值。可用來做為調整的依據。
  • div.node-class [選填|不可重複]
    當此節點顯示成表格(不論是可編輯的表單或顯示用的表格),該表格的class會設定這個值。可用來做為調整的依據。
  • div.node-title [必填|不可重複]
    標題,可空白,但如果不填的話可能會出錯。
  • div.node-repeatable:true | false* [選填|不可重複]
    可否重複,值有「true」跟「false」,預設是false。
  • div.node-content-temp:子節點 [選填|不可重複]
    這是預設子節點的內容,當可重複時,新增的重複資料會取用此預設子節點的內容。當node-content-temp不存在時,程式會自動抓取node-contents最後一個來作為node-content-temp。
  • div.node-contents:子節點 [選填|可重複]
    要儲存並顯示的子節點內容,可重複代表著可以設定很多相同或不同的子節點內容。當node-contents不存在時,程式會自動抓取node-content-temp來作為node-contents。

node的舉例如下:(紅色部份表示是值)

<div class="node">
  <div class="node-type">node</div>
  <div class="node-title">教師</div>
  <div class="node-repeatable">true</div>
  <div class="node-content-temp">
    <div class="node">
      ……
    </div>
    <div class="node">
      ……
    </div>

  </div>
  <div class="node-contents">
    <div class="node">
      ……
    </div>
    <div class="node">
      ……
    </div>
  </div>
  <div class="node-contents">
    <div class="node">
      ……
    </div>
    <div class="node">
      ……
    </div>
  </div>
  <div class="node-contents">
    <div class="node">
      ……
    </div>
    <div class="node">
      ……
    </div>

  </div>
</div>
input 輸入表單
  • div.node-type:input [必填|不可重複]
    節點的類型,如果你要宣告一個是input,請在此輸入「input」。是node的話就輸入「node」。
  • div.node-id [選填|不可重複]
    當此節點顯示成表格(不論是可編輯的表單或顯示用的表格),該表格的id會設定這個值。可用來做為調整的依據。
  • div.node-class [選填|不可重複]
    當此節點顯示成表格(不論是可編輯的表單或顯示用的表格),該表格的class會設定這個值。可用來做為調整的依據。
  • div.node-title [必填|不可重複]
    標題,可空白,但這樣可能會有錯誤。
  • div.node-repeatable:true | false* [選填|不可重複]
    可否重複,值有「true」跟「false」,預設是false。
  • div.input-required:true | false* [選填|不可重複]
    當表單內沒有值的時候,會提示使用者要填寫,但目前仍無法阻止使用者在沒有在表單輸入值的時候遞交表單。值有「true」跟「false」,預設是false。
  • div.input-type:onebox | date | textarea* | texteditor | fileupload | dropdown | list [選填|不可重複]
    表單的類型,目前只有七種,也跟DSpace的輸入類型一樣。如果設定為dropddown或list的話,則必須填寫下面的input-options。
  • select.input-options:<option value="VALUE">TITLE</option> [選填|不可重複]
    下拉式選單的選項,內容值要填入跟<select>內容一樣的<option>標籤。如果在input-options裡面如果沒有以defalut-value、values為值的<option>標籤,那程式會自動新增。
  • div.intput-default-value [選填|不可重複]
    預設值,在重複的時候會自動帶入此值,可以輸入空白。當default-value沒有資料的時候,會自動取用最後一個values的值作為default-value。
  • div.intput-values [選填|可重複]
    要儲存的值。當values沒有資料的時候,會自動取用default-value的值作為values。

input範例:(紅色部份表示值)

<div class="node">
  <div class="node-type">input</div>
  <div class="node-title">作品語言</div>
  <div class="node-repeatable">true</div>
  <div class="input-required">true</div>
  <div class="input-type">dropdown</div>
  <select class="input-options">
    <option value="zh_TW">中文</option>
    <option value="en">English</option>
    <option value="jp">日本語</option>
  </select>
  <div class="input-default-value">zh_TW</div>
  <div class="input-values">zh_TW</div>
  <div class="input-values">jp</div>
</div>

範例與即時適用

&<div class="xml-root" id="xmlRootExample" style="display:none">
  <div class="node">
  <div class="node-type">node</div>
  <div class="node-title">秋番動畫</div>
  <div class="node-repeatable">true</div>
  <div class="node-content-temp">
    <div class="node">
      <div class="node-type">input</div>
      <div class="node-title">作品題名</div>
      <div class="node-repeatable">true</div>
      <div class="input-required">true</div>
      <div class="input-type">onebox</div>
      <div class="input-default-value"></div>
    </div>
    <div class="node">
      <div class="node-type">input</div>
      <div class="node-title">作品語言</div>
      <div class="node-repeatable">false</div>
      <div class="input-required">true</div>
      <div class="input-type">dropdown</div>
      <select class="input-options">
        <option value="zh_TW">中文</option>
        <option value="en">English</option>
        <option value="jp">日本語</option>
      </select>
      <div class="input-default-value">zh_TW</div>
    </div>  
  </div>
  <div class="node-contents">
    <div class="node">
      <div class="node-type">input</div>
      <div class="node-title">作品題名</div>
      <div class="node-repeatable">true</div>
      <div class="input-required">true</div>
      <div class="input-type">texteditor</div>
      <div class="input-default-value"></div>
      <div class="input-values">神薙</div>
      <div class="input-values">kaminagi</div>
    </div>
    <div class="node">
      <div class="node-type">input</div>
      <div class="node-title">作品語言</div>
      <div class="node-repeatable">false</div>
      <div class="input-required">true</div>
      <div class="input-type">list</div>
      <select class="input-options">
        <option value="zh_TW">中文</option>
        <option value="en">English</option>
        <option value="jp">日本語</option>
      </select>
      <div class="input-default-value">zh_TW</div>
      <div class="input-values">jp</div>
    </div>
  </div>
</div>

※fileupload要搭配DSpace才能使用,FCKeditor找不到空間上傳暫時,因此texteditor也不能用,請多見諒


輸出到<default-value>

如果想知道怎麼運作的,可以直接看看原始碼喔。


在input-forms.xml設定xmlmetadata

跟一般的field不同的是,xmlmetadata會需要輸入<default-value>。要注意的是,<default-value>裡面的資料不允許斷行,可以利用上面的輸出成<default-value>程式過濾器來輸入。

把上面的範例輸入進去之後,<field>的程式碼會像是這個樣子:

修改完畢之後重新啟動Tomcat就可以了。


未完成的部份

  • 驗證階段也要顯示xmlmetadata。

可以參考display-item.jsp的修改去做,加油吧。

(more...)

DSpace新增input-type——FileUpload篇

布丁布丁吃布丁

DSpace新增input-type——FileUpload篇

DSpace新增input-type目錄

  1. 安裝篇
  2. TextEditor篇
  3. FileUpload篇
  4. XMLMetadata篇

圖示前面也貼過了,使用上也跟TextEditor一樣,故這邊講述一下使用上的限制、建議修改方向以及目前已知的Bug。


限制一:必須勾選「文件由一個以上的 檔案所組成」

image

如果沒有勾選的話,在編輯metadata的階段會提示你要回去勾選。

image 

這是偷懶的程式設計,希望可以改善。

因為我想說如果很多欄位都要上傳檔案,那麼限定只能上傳一個檔案一定會出問題。因此我只修改在允許多檔案上傳所使用到的程式碼,並沒有去考慮到不允許多檔案上傳時候的情況。

改善的方案有兩個:

方案一:預先勾選「文件由一個以上的 檔案所組成」

雖然我們可以簡單地修改[dspace-jspui-webapp]/submit/initial-questions.jsp來讓一開史的問題時就對這一項打勾,但我覺得應該是要修改DSpace內部的預設參數才對。

關於多檔案上傳的設定,在之前的修改當中在edit-metadata.jsp有段程式碼可以略知一二:

boolean hasMultipleFiles = si.getSubmissionItem().hasMultipleFiles();

在回溯一連串的程式碼之後,會在[dspace-api]/workflow\WorkflowItem.java裡面看到hasMultipleFiles()其實是去取得"multiple_files"欄位的值。如果能將這個欄位的預設值改成true,應該會比起只改initial-questions.jsp還保留了些靈活性。

方案二:調整fileupload程式,依照判斷是否能夠上傳一個以上的檔案。

比起前者,這挑戰較為困難,但卻是比較好的解決方案。

大致上的規劃是:

  1. 先取得「已經上傳檔案」跟「是否只能上傳多個檔案」變數
  2. 如果「已經有上傳檔案」而且「不能上傳多個檔案」,則隱藏fileupload表單並提示訊息,否則顯示全部的fileupload表單。
  3. 上傳之後,如果是「不能上傳多個檔案」,則隱藏其他的fileupload表單,並顯示提示訊息。
  4. 刪除檔案的時候,如果是「不能上傳多個檔案」,則顯示其他fileupload表單。

如果我有時間的話,我會朝向這個方向去改善fileupload。當然如果有人能自己這樣改寫的話是最好的了!


限制二:一定要經過上傳階段。

如果把上傳階段放在敘述階段後面,那麼上傳檔案通常會失敗。但如果把上傳階段放在敘述階段前面,使用者又會對這個上傳階段不知道該傳些什麼好感到困惑。而且上面的ProcessBar有著上傳、驗證階段也有上傳的部份,都一樣會讓使用者感到困惑。

要修正這個問題有幾個方案:

方案一:將上傳階段放在step=2,並隱藏上傳階段。

建議修改的部份有:

  • 上傳階段用JavaScript跳過(還要考慮上一步、下一步的影響)
  • [dspace-jspui-webapp]/submit/progressbar.jsp隱藏上傳的階段
  • [dspace-jspui-webapp]/submit/verify-prune.jspu隱藏上傳檔案的訊息。

但這些修改會大大地影響DSpace原來的遞交機制,我不是很贊成。

方案二:將上傳階段放在step=3,並在網址參數擁有「action=json」的情況下接受未到此步驟也能進行上傳。

這就要研究DSpace的遞交作業的結構去做修改,因為需要動到Java而重編譯,所以難度較高也較花時間,但我建議從此著手應該是比較好的。至於我也還沒研究到DSpace的遞交作業結構,所以無法建議可以修改的方向。


限制三:敘述階段跟上傳階段中檔案不同步的狀況。

在敘述階段當中,如果沒有按「下一步」、「上一步」或「取消儲存」按鈕做遞交(submit)的動作,就沒辦法把剛剛上傳檔案之後取得的資料儲存。因此如果上傳了檔案,卻沒有遞交而直接離開此畫面,就會造成上傳階段檔案列表裡面有該檔案、但描述階段卻沒有該檔案的情況。儘管這可以用JavaScript的事件onunload來避免使用者在未遞交的情況下離開網頁,但這對IE6似乎沒有效果,最後我只好作罷。

而如果在上傳階段刪去特定檔案的話,目前也不會同步刪除敘述階段裡面的資料。這可能還要對Submission做一番的研究才行。

遺憾的是,在熟悉Submission之前,我也想不到什麼有效的建議,要做到完全同不實在是很困難。最下策就是接受不同步的這個Bug,希望使用者能夠適應。當然,我並不樂見於此,因此希望有時間或有人能夠改善這個問題。


總體而言,FileUpload仍然不算完成,問題還是很多,目前也只有堪用的地步而已。

如果你有心繼續研究,請別忘了調整[dspace-jspui-webapp]/extension/ajaxfileupload/ajaxfileupload_fn.js裡面的參數。

繼續努力吧,下一篇終於要談到XMLMetadata了。

(more...)

DSpace新增input-type——TextEditor篇

布丁布丁吃布丁

DSpace新增input-type——TextEditor篇

DSpace新增input-type目錄

  1. 安裝篇
  2. TextEditor篇
  3. FileUpload篇
  4. XMLMetadata篇

imageTextEditor的操作畫面

image查核的畫面

image 結果呈現

使用步驟1:修改input-forms.xml

DSpace使用input-forms.xml來規範遞交作業中編輯Metadata的表單呈現,input-forms.xml檔案位於[dspace]/config/input-forms.xml。([dspace]等路徑縮寫說明,請參考前一篇:DSpace新增input-type——安裝篇)

image

你可以將摘要(dc.description.abstract)欄位(field)的輸入型態(input-type)改成「texteditor」,並覆蓋掉原本[dspace]/config/input-forms.xml。fileupload也是一樣的作法,唯有xmlmetadata需要新增一個default-value欄位來預先規範其表單呈現的方式,這點我們稍後再討論。

詳細的input-forms.xml的設定,可以參考文件當中的DSpace System Documentation: Submission Customization這一章。

使用步驟2:重新啟動Tomcat伺服器

雖然前一篇有提過了,但這邊還是騙點稿費(有這東西?),再紀錄一下:

  1. 停止你的Tomcat伺服器
    > cd [tomcat]/bin/
    > ./shutdown.sh
  2. 重新啟動Tomcat伺服器
    > ./startup.sh

請稍待半分鐘等DSpace恢復正常運作,再連線進入遞交作業,就可以看到TextEditor了。


TextEditor的設定

與其說是設定TextEditor,應該說就跟設定FCKeditor是一樣的,之前的DSpace教學講座(四):修改實例DSpace教學講座(四):修改實例之修正TextEditor都有探討到如何安裝、修改,最後結果就是這個TextEditor。

跟FCKeditor設定一樣的,我們可以藉由修改[dspace-jspui-webapp]/extension/fckeditor/fckconfig.js檔案來調整FCKeditor的功能及外表呈現。詳細說明可以參考FCKeditor Docs,在此我提幾個比較值得注意的設定:

工具列
FCKConfig.ToolbarSets["Default"] = [
    ['Source','DocProps','-','Save','NewPage','Preview','-','Templates'],
    ['Cut','Copy','Paste','PasteText','PasteWord','-','Print','SpellCheck'],
    ['Undo','Redo','-','Find','Replace','-','SelectAll','RemoveFormat'],
    ['Form','Checkbox','Radio','TextField','Textarea','Select','Button','ImageButton','HiddenField'],
    '/',
    ['Bold','Italic','Underline','StrikeThrough','-','Subscript','Superscript'],
    ['OrderedList','UnorderedList','-','Outdent','Indent','Blockquote','CreateDiv'],
    ['JustifyLeft','JustifyCenter','JustifyRight','JustifyFull'],
    ['Link','Unlink','Anchor'],
    ['Image','Flash','Table','Rule','Smiley','SpecialChar','PageBreak'],
    '/',
    ['Style','FontFormat','FontName','FontSize'],
    ['TextColor','BGColor'],
    ['FitWindow','ShowBlocks','-','About']        // No comma for the last row.
] ;

預設的工具列相當地多,有些不使用的功能可以關閉。

File Browser connector與Quick Upload "uploader"

image File Browser

image Quick Upload

這是FCKeditor內建的上傳功能,但FCKeditor僅支援asp、aspx、cfm、lasso、perl、php、py,卻沒有DSpace所使用的JSP。因此使用上不是另外找其他伺服器來跟FCKeditor搭配使用,或是關閉掉上傳的功能,再要不然就是想辦法結合DSpace的上傳功能來使用了。


下一篇繼續講input-type: fileupload。

(more...)

DSpace新增input-type——安裝篇

布丁布丁吃布丁

DSpace新增input-type——安裝篇

DSpace新增input-type目錄

  1. 安裝篇
  2. TextEditor篇
  3. FileUpload篇
  4. XMLMetadata篇

目前的DSpace 1.4.2~1.5雖然提供了很多input-type,像onebox(單欄)、textarea(多行)、dropdown(下拉式),但是使用起來還是覺得不夠,尤其是資料儲存無法像樹狀結構一樣靈活,在設計Metadata的時候更是一個很大的限制(雖然很多架設DSpace的人都沒有更動過input-forms的樣子)。

為了改進DSpace的不足,我以DSpace 1.5版開發出以下三種輸入表單型態:

texteditor

把原本的textarea以FCKeditor取代,並設定自動隱藏工具列。最後輸出資料則是html程式碼。

image

fileupload

在edit-metadata的步驟中,透過AJAX技術直接使用DSpace上傳步驟的功能。

上傳前:image

上傳後:image

xmlmetadata

可允許樹狀結構的metadata,並允許特定節點重複,可更靈活地儲存資料。一樣具備onebox、dropdown、textarea、texteditor、fileupload等多種輸入類型。

image

此篇介紹如何修改DSpace 1.5的各個檔案,以呈現出以上的功能,使用與設定則留到其他篇再講吧!


安裝環境與路徑縮寫說明

DSpace 1.5版本跟之前的1.4.2版本各程式存放的路徑上有很大的差別,此處我以1.5版為主,安裝在CentOS 5 Final上,JSP伺服器是Apache Tomcat 6.0.16

以下說明各路徑的簡寫:

[dspace-source]:DSpace原始檔的位置,通常位於「/opt/dspace-1.5.0-src-release/」

[dspace-jspui-webapp]:JSPUI原始碼路徑,通常位於「[dspace-source]/dspace-jspui/dspace-jspui-webapp/src/main/webapp」之中。根據文件說明,修改之後存放的檔案請放到「[dspace-source]/dspace/modules/jspui/src/main/webapp/」之中。

[dspace-jspui-api]:JSPUI的API,通常位於「/opt/dspace-1.5.0-src-release/dspace-jspui/dspace-jspui-api/src/main/java/org/dspace/app/webui/」。

[dspace-api]:DSpace的API,許多核心程式都擺在這邊,通常位於「[dspace-source]/dspace-api/src/main/java/org/dspace/」

[dspace]:DSpace安裝完成之後的目的地,包括input-forms.xml設定檔都要在這邊修改。預設的位置是「/dspace/」。

[tomcat]:JSP伺服器的安裝位置,通常是在「/opt/apache-tomcat-6.0.16/」裡面。

那麼以下就是安裝與修改的步驟囉!我會列出「路徑與檔名」、「我的程式碼下載」、「程式碼說明」以方便大家修改或安裝。因為細節繁雜,所以有可能有所遺漏,請在下方的回應欄盡量批評吧!


[dspace-jspui-webapp]/extension/...... (extension.20081018.zip)

這是一個新開啟的資料夾,裡面擺放我撰寫的JavaScript、CSS程式,請下載附件extension.zip,並擺放到該路徑去。裡面又分成幾個部份:(以下檔案所指的根目錄就是「[dspace-jspui-webapp]/extension/」,之後其他敘述也同這個道理)

/jquery.js:JavaScript的函式庫jQuery,在此使用的是1.2.1版本,讓JavaScript程式撰寫起來輕鬆很多,我撰寫的JavaScript都會用到jQuery。

/fckeditor/FCKeditor 2.6.3版本,一種JavaScript的網頁內嵌所見即得編輯器,可用類似Word的操作介面、建立HTML程式碼。用於input-type: texteditor,可改善DSpace 1.5只能顯示單調文字的缺點。

/fckeditor/fckeditor_display_toggle.js:用來自動隱藏/顯示工具列的函式。

/ajaxfileupload/ajaxfileupload.js:jQuery的Plugin,我改寫成適合DSpace檔案上傳的版本。

/ajaxfileupload/ajaxfileupload_fn.js:供input-type: fileupload呼叫ajaxfileupload.js的函式,裡面可設定fileupload的參數,但照以下安裝步驟的話是不需要修改此設定的。

/ajaxfileupload/ajaxfileupload_fnXML.js:給XMLMetadata呼叫ajaxfileupload.js的函式。

/xmlmetadata/dspace-inputtype-xml.js:XMLMetadata主要的JavaScript函式程式碼。

/xmlmetadata/dspace-inputtype-xml.css:XMLMetadata在呈現表單跟表格時的樣式表。

/xmlmetadata/ui.datepicker.js:jQuery的UI:Datepicker,可以方便挑選日期,用於XMLMetdata的input-type: date,我想這應該比DSpace原本的date好用很多。

/xmlmetadata/flora.datepicker.css:Datepicker的樣式表。

/display-item/metadata-value-display.js:在Display Item頁面時,針對fileupload跟xmlmetadata兩種資料調整呈現的樣貌。

這些程式只用短短的半個月時間寫完,也還沒做嚴格的測試,如果之後有改良的版本,會直接在此更新,並加上日期版本。


[dspace-jspui-webapp]/submit/...... (submit.20081015.zip)

這邊我修改了在進行上傳步驟的程式碼,以下一一說明。

/edit-metadata.jsp

image

DSpace描述階段的程式碼,為了能夠新增這三種input-type,在這一頁下了很多功夫。以下是修改細項:

修改1:引用所需的JavaScript跟CSS檔案。 請在<dspace>標籤開始之後插入這些引用吧。(紅色部份是新增的程式碼)

<dspace:layout locbar="off" navbar="off" titlekey="jsp.submit.edit-metadata.title">

<script type="text/javascript" src="<%= request.getContextPath() %>/extension/jquery.js"></script>
<script type="text/javascript" src="<%= request.getContextPath() %>/extension/fckeditor/fckeditor.js"></script>
<script type="text/javascript" src="<%= request.getContextPath() %>/extension/ajaxfileupload/ajaxfileupload.js"></script>
<script type="text/javascript" src="<%= request.getContextPath() %>/extension/ajaxfileupload/ajaxfileupload_fn.js"></script>
<script type="text/javascript" src="<%= request.getContextPath() %>/extension/ajaxfileupload/ajaxfileupload_fnXML.js"></script>
<script type="text/javascript" src="<%= request.getContextPath() %>/extension/fckeditor/fckeditor_display_toggle.js"></script>
<script type="text/javascript" src="<%= request.getContextPath() %>/extension/xmlmetadata/dspace-inputtype-xml.js"></script>
<script type="text/javascript" src="<%= request.getContextPath() %>/extension/xmlmetadata/ui.datepicker.js"></script>
<link rel="stylesheet" href="<%= request.getContextPath() %>/extension/xmlmetadata/flora.datepicker.css" type="text/css" media="screen" title="Flora (Default)">
<link rel="stylesheet" href="<%= request.getContextPath() %>/extension/xmlmetadata/dspace-inputtype-xml.css" type="text/css" media="screen">

修改2:定義要丟給新增函式的變數。包括workspaceItem的ID(目前遞交作業的ID)、nonInternalBistreams的ID(哪些是公開的檔案)、hasMultipleFiles(是否允許多檔案上傳)、defaultValue(XMLMetadata的預設呈現樣貌)。請加在設定inputType、label跟closedVocabulary的後面吧。(紅色部份是新增的程式碼)



       String label = inputs[z].getLabel();
       boolean closedVocabulary = inputs[z].isClosedVocabulary();

>     //For fileupload
     int workspaceItemID = si.getSubmissionItem().getID();
     Bitstream[] bitstreams = si.getSubmissionItem().getItem().getNonInternalBitstreams();
     ArrayList nonInternalBistreamsID = new ArrayList();  //new Int(bitstreams);
     for (int i = 0; i < bitstreams.length; i++)
         nonInternalBistreamsID.add(bitstreams[i].getID());
     boolean hasMultipleFiles = si.getSubmissionItem().hasMultipleFiles();
    
     //For XMLMetadata & other
     String defaultValue = inputs[z].getDefaultValue();

修改3:新增input-type呼叫函式的判斷式。在下方有if else的判斷式,程式會依照input-type的值不同,呼叫不同的函式,那麼我們就把這三個新增的函式也加入判斷之中,新增在list跟onebox之間吧。(紅色部份是新增的程式碼)

       else if (inputType.equals("list"))
       {
          doList(out, item, fieldName, dcSchema, dcElement, dcQualifier,
               repeatable, inputs[z].getPairs(), label);
       }
     else if (inputType.equals("texteditor"))
       {
         doTextEditor(out, item, fieldName, dcSchema, dcElement, dcQualifier,
                repeatable, fieldCountIncr, label, pageContext, vocabulary,
               closedVocabulary, request.getContextPath() + "/extension/fckeditor/");
       }
     else if (inputType.equals("fileupload"))
       {
         doFileUpload(out, item, fieldName, dcSchema, dcElement, dcQualifier,
              repeatable, fieldCountIncr, label, pageContext, vocabulary,
              closedVocabulary, workspaceItemID, nonInternalBistreamsID,
           hasMultipleFiles);
       }
     else if (inputType.equals("xmlmetadata"))
       {
         doXMLMetadata(out, item, fieldName, dcSchema, dcElement, dcQualifier,
                repeatable, fieldCountIncr, label, pageContext, vocabulary,
               closedVocabulary, workspaceItemID,
            nonInternalBistreamsID, hasMultipleFiles, defaultValue);
       }

       else
       {
         doOneBox(out, item, fieldName, dcSchema, dcElement, dcQualifier,
              repeatable, fieldCountIncr, label, pageContext, vocabulary,
              closedVocabulary);
       }

修改4:宣告doTextEditor()、doFileUpload()跟doXMLMetadata()。請寫在doList結尾之後吧(紅色的部份是新增的程式碼):

          }//end doList

    void doTextEditor(javax.servlet.jsp.JspWriter out, Item item,
      String fieldName, String schema, String element, String qualifier, boolean repeatable,
      int fieldCountIncr, String label, PageContext pageContext, String vocabulary, boolean closedVocabulary, String sBasePath)
      throws java.io.IOException
    {

      DCValue[] defaults = item.getMetadata(schema, element, qualifier, Item.ANY);
      int fieldCount = defaults.length + fieldCountIncr;
      StringBuffer sb = new StringBuffer();
      String val;

      if (fieldCount == 0)
         fieldCount = 1;

      for (int i = 0; i < fieldCount; i++)
      {
   if (i == 0)
      sb.append("<tr><td class=\"submitFormLabel\">")
        .append(label)
        .append("</td>");
   else
      sb.append("<tr><td> </td>");

         if (i < defaults.length)
           val = defaults[i].value;
         else
           val = "";

         sb.append("<td colspan=\"2\">");

         String textareaName = fieldName;
         if (repeatable && i>0)
            textareaName = textareaName + "_" + i;

         sb.append("<textarea name=\"")
           .append(fieldName);
         if (repeatable && i>0)
           sb.append("_").append(i);
         sb.append("\" rows=\"4\" cols=\"45\"")
           .append(hasVocabulary(vocabulary)&&closedVocabulary?" readonly=\"readonly\" ":"")
           .append("   >")
           .append(val)
     .append("</textarea>")
     .append(doControlledVocabulary(fieldName + (repeatable?"_" + i:""), pageContext, vocabulary));
      

       //FCKeditor ReplaceTextarea
      
       sb.append("<script type=\"text/javascript\">\n")
         .append("  var oFCKeditor = new FCKeditor( \"")
           .append(fieldName);
         if (repeatable && i>0)
           sb.append("_").append(i);
         sb.append("\" ) ;\n")
       .append("  oFCKeditor.Config[\"ToolbarStartExpanded\"] = false ;\n")
         .append("  oFCKeditor.BasePath  = \""+sBasePath+"\" ;\n")
         .append("  oFCKeditor.ReplaceTextarea() ;\n")
         .append("</script>\n");

     sb.append("</td>\n");

   if (repeatable && i < defaults.length)
   {
      // put a remove button next to filled in values
      sb.append("<td><input type=\"submit\" name=\"submit_")
        .append(fieldName)
        .append("_remove_")
        .append(i)
//        .append("\" value=\"Remove This Entry\"/> </td></tr>");
        .append("\" value=\"")
           .append(LocaleSupport.getLocalizedMessage(pageContext, "jsp.submit.edit-metadata.button.remove"))
           .append("\"/> </td></tr>");
   }
   else if (repeatable && i == fieldCount - 1)
   {
      // put a 'more' button next to the last space
      sb.append("<td><input type=\"submit\" name=\"submit_")
        .append(fieldName)
//        .append("_add\" value=\"Add More\"/> </td></tr>");
        .append("_add\" value=\"")
        .append(LocaleSupport.getLocalizedMessage(pageContext, "jsp.submit.edit-metadata.button.add"))
        .append("\"/> </td></tr>");
   }
   else
   {
      // put a blank if nothing else
      sb.append("<td> </td></tr>");
   }
      }

      out.write(sb.toString());
    }  //void doTextEditor()

    void doFileUpload(javax.servlet.jsp.JspWriter out, Item item,
      String fieldName, String schema, String element, String qualifier, boolean repeatable,
      int fieldCountIncr, String label, PageContext pageContext, String vocabulary, boolean closedVocabulary,
    int workspaceItemID, ArrayList nonInternalBistreamsID, boolean hasMultipleFiles)
      throws java.io.IOException
    {

      DCValue[] defaults = item.getMetadata(schema, element, qualifier, Item.ANY);
    fieldCountIncr = 1;
      int fieldCount = defaults.length + fieldCountIncr;
      StringBuffer sb = new StringBuffer();
      String val;
    
      if (fieldCount == 0)
         fieldCount = 1;

      for (int i = 0; i < fieldCount; i++)
      {
   if (i == 0)
      sb.append("<tr><td class=\"submitFormLabel\">")
        .append(label)
        .append("</td>");
   else
      sb.append("<tr><td> </td>");

         if (i < defaults.length)
           val = defaults[i].value.replaceAll("\"", """);
         else
           val = "";
      
    if (!val.equals(""))
    {
      String[] tempID = val.split("/");
      if (tempID.length > 2)
      {
        //String id = tempID[(tempID.length - 2)];
      int id = Integer.parseInt(tempID[(tempID.length - 2)]);
      
      int index = nonInternalBistreamsID.indexOf(id);
      if (index == -1)  continue;
      }
    }
      
         String inputName = fieldName;
         if (repeatable && i>0)
           inputName = inputName + "_" + i;


       sb.append("<td colspan=\"2\">");

    if (hasMultipleFiles == true)
    {
    sb.append("\n<input type=\"text\" id=\""+inputName+"\" name=\""+inputName+"\" value=\""+val+"\" style=\"display:none\"   />\n")
      .append("  <input type=\"file\" onchange=\"jQuery(this).nextAll('button.fileupload-do:first').click()\" />\n")
      .append("  <span></span>\n")
      .append("  <button class=\"fileupload-do\" onclick=\"return ajaxFileUpload(this, "+workspaceItemID+", '"+label+"');\" type=\"button\">Upload</button>\n");
      
       if (!val.equals(""))
       {
      sb.append("  <script type=\"text/javascript\">")
        .append("  ajaxFileUploadExist(\""+inputName+"\");")
        .append("</script>");
       }
    }  //if (hasMultipleFiles == true)
    else
    {
      sb.append("Please check \""+LocaleSupport.getLocalizedMessage(pageContext, "jsp.submit.initial-questions.elem3")+"\"! \n");
    }
       sb.append("</td>\n");
    

   if (repeatable && i < defaults.length)
   {
      // put a remove button next to filled in values
      sb.append("<td><input type=\"submit\" name=\"submit_")
        .append(fieldName)
        .append("_remove_")
        .append(i)
//        .append("\" value=\"Remove This Entry\"/> </td></tr>");
        .append("\" value=\"")
           .append(LocaleSupport.getLocalizedMessage(pageContext, "jsp.submit.edit-metadata.button.remove"))
           .append("\" style=\"display:none\" ")
      .append("/>")
      .append("<button type=\"button\" onclick=\"ajaxFileUploadRemove(this, "+workspaceItemID+")\">"+LocaleSupport.getLocalizedMessage(pageContext, "jsp.submit.edit-metadata.button.remove")+"</button>")
      .append(" </td></tr>");
   }
   else if (repeatable && i == fieldCount - 1)
   {
      // put a 'more' button next to the last space
      sb.append("<td><input type=\"submit\" name=\"submit_")
        .append(fieldName)
//        .append("_add\" value=\"Add More\"/> </td></tr>");
        .append("_add\" value=\"")
        .append(LocaleSupport.getLocalizedMessage(pageContext, "jsp.submit.edit-metadata.button.add"))
        .append("\"/> </td></tr>");
   }
   else
   {
      // put a blank if nothing else
      sb.append("<td>")
      .append("  <button class=\"fileupload-cancel\" onclick=\"return ajaxFileUploadCancel(this, "+workspaceItemID+")\" style=\"display:none\" type=\"button\">"+LocaleSupport.getLocalizedMessage(pageContext, "jsp.submit.edit-metadata.button.remove")+"</button>\n")
      .append(" </td></tr>");
   }
      }

      out.write(sb.toString());
    }  //end of doFileUpload

    void doXMLMetadata(javax.servlet.jsp.JspWriter out, Item item,
      String fieldName, String schema, String element, String qualifier, boolean repeatable,
      int fieldCountIncr, String label, PageContext pageContext, String vocabulary, boolean closedVocabulary,
    int workspaceItemID, ArrayList nonInternalBistreamsID, boolean hasMultipleFiles, String defaultValue)
      throws java.io.IOException
    {

      DCValue[] defaults = item.getMetadata(schema, element, qualifier, Item.ANY);
      int fieldCount = defaults.length + fieldCountIncr;
      StringBuffer sb = new StringBuffer();
      String val;

      if (fieldCount == 0)
         fieldCount = 1;

      for (int i = 0; i < fieldCount; i++)
      {
   if (i == 0)
      sb.append("<tr><td class=\"submitFormLabel\">")
        .append(label)
        .append("</td>");
   else
      sb.append("<tr><td> </td>");

         if (i < defaults.length)
           val = defaults[i].value;
         else if (defaultValue != null)
           val = defaultValue;
     else
       val = "";
    
         sb.append("<td colspan=\"2\">");

         String textareaName = fieldName;
         if (repeatable && i>0)
            textareaName = textareaName + "_" + i;

         sb.append("<textarea id=\""+textareaName+"\" name=\"")
           .append(fieldName);
         if (repeatable && i>0)
           sb.append("_").append(i);
         sb.append("\" rows=\"4\" cols=\"45\"")
           .append(hasVocabulary(vocabulary)&&closedVocabulary?" readonly=\"readonly\" ":"")
           .append("   >")
           .append(val)
     .append("</textarea>")
     .append(doControlledVocabulary(fieldName + (repeatable?"_" + i:""), pageContext, vocabulary));
      

       //XMLMetadata Form Creator
       if (!val.equals(""))
     {
       sb.append("\n\n<script type=\"text/javascript\">\n")
         .append("var xm = new XMLMetadata(\""+textareaName+"\");\n")
         .append("xm.workspaceItemID = "+workspaceItemID+";\n")
         .append("xm.fieldTitle = '"+label+"';\n");
       if (hasMultipleFiles == false)
       {
         sb.append("xm.hasMultipleFiles = false;\n");
        sb.append("xm.langNotHasMultipleFiles = \"Please check \'"+LocaleSupport.getLocalizedMessage(pageContext, "jsp.submit.initial-questions.elem3")+"\'!\";\n");
       }
      
       if (nonInternalBistreamsID.size() != 0)
       {
         String nIBIDary = "[";
         for (int b = 0; b < nonInternalBistreamsID.size(); b++)
         {
          if (b > 0)
            nIBIDary = nIBIDary + ",";
          nIBIDary = nIBIDary + nonInternalBistreamsID.get(b);
         }
         nIBIDary = nIBIDary + "]";
         sb.append("xm.nonInternalBistreamsID = "+nIBIDary+";\n");
       }
       sb.append("xm.createRootForm();\n");
       sb.append("</script>\n");
      }

     sb.append("</td>\n");

   if (repeatable && i < defaults.length)
   {
      // put a remove button next to filled in values
      sb.append("<td><input type=\"submit\" name=\"submit_")
        .append(fieldName)
        .append("_remove_")
        .append(i)
//        .append("\" value=\"Remove This Entry\"/> </td></tr>");
        .append("\" value=\"")
           .append(LocaleSupport.getLocalizedMessage(pageContext, "jsp.submit.edit-metadata.button.remove"))
           .append("\"/> </td></tr>");
   }
   else if (repeatable && i == fieldCount - 1)
   {
      // put a 'more' button next to the last space
      sb.append("<td><input type=\"submit\" name=\"submit_")
        .append(fieldName)
//        .append("_add\" value=\"Add More\"/> </td></tr>");
        .append("_add\" value=\"")
        .append(LocaleSupport.getLocalizedMessage(pageContext, "jsp.submit.edit-metadata.button.add"))
        .append("\"/> </td></tr>");
   }
   else
   {
      // put a blank if nothing else
      sb.append("<td> </td></tr>");
   }
      }

      out.write(sb.toString());
    }  //void doXMLMetadata()

%>

doTextEditor()跟doXMLMetadata()是參考doTextArea()而來的,而doFileUpload()則是參考doOneBox()。

修改5:移除doPersonalName()、doSeriesNumber()跟doTwoBox()的寬度限制。不知為何這些函式都把表格寬度鎖死了,他們裡面都會擁有類似的程式碼,我舉doPersonalName()的設定為例:(紅色為刪除寬度限制的程式碼,附近的註解則是原本有限制寬度的程式碼)

      //Width hints used here to affect whole table
      headers.append("<tr><td> </td>") //.append("<tr><td width=\"40%\"> </td>")
             //.append("<td class=\"submitFormDateLabel\" width=\"5%\">")
       .append("<td class=\"submitFormDateLabel\">")
//             .append("Last name<br>e.g. <strong>Smith</strong></td>")
       .append(LocaleSupport.getLocalizedMessage(pageContext, "jsp.submit.edit-metadata.lastname"))
       .append("</td>")
             //.append("<td class=\"submitFormDateLabel\" width=\"5%\">")
      .append("<td class=\"submitFormDateLabel\">")
//             .append("First name(s) + \"Jr\"<br> e.g. <strong>Donald Jr</strong></td>")
       .append(LocaleSupport.getLocalizedMessage(pageContext, "jsp.submit.edit-metadata.firstname"))
       .append("</td>")
             //.append("<td width=\"40%\"> </td>")
      .append("<td> </td>")
             .append("</tr>");
      out.write(headers.toString());

修改6:修改欄位寬度配置。每一列輸入欄位中間都會間隔一個調整欄位寬度用的列,我們可以藉由調整這一列來控制欄位大小,但前提是要先移除掉上面那幾個函式裡面的寬度限制。(紅色部份是修改過後的值,表示左右各保留10%留白,中間則是有80%的寬度。)

<%-- HACK: Using this line to give the browser hints as to the widths of cells --%>
       <tr>
         <td width="10%"> </td>
         <td colspan="2" width="80%"> </td>
         <td width="10%"> </td>
       </tr>
/upload-file-list.jsp跟/get-file-format.jsp

image upload-file-list.jsp

image get-file-format.jsp

前者上傳檔案之後的列表、後者是當上傳檔案格式不明時會提示使用者選擇檔案格式的網頁。當這些網頁被讀取時,我想要轉換成以json格式呈現剛剛上傳檔案的資料(json是JavaSciprt常用的輕量級資料語言)。要讓upload-file-list.jsp跟get-file-format.jsp正常運作,必需要傳送以下參數:

  • workspace_item_id:遞交階段的ID,可由si.getSubmissionItem().getID()取得,這段程式碼寫在edit-metadata的修改2當中。
  • step:上傳檔案的階段,預設是「3」,以下教學會將之改成「2」。可在ajaxfileupload_fn.js裡面設定。
  • page:上傳檔案只會有1頁,輸入「1」即可。可在ajaxfileupload_fn.js裡面設定。
  • jsp:預設是由choose-file.jsp傳送過去,輸入值為「/submit/choose-file.jsp」即可。可在ajaxfileupload_fn.js裡面設定。
  • description:上傳檔案的敘述,可空白或不傳,我在fileupload的設定是會傳label的值。可在ajaxfileupload.js裡面設定。
  • submit_upload:要有值程式才知道是上傳的步驟,我則是固定填入「&gt;」。可在ajaxfileupload.js裡面設定。

修改:用網址設定呈現時轉址到顯示json的網頁。如果網址中包含「action=json」,那麼我就轉到另一個顯示json的網頁去。轉址的這段程式碼插入在<dspace>標籤前後,細節如下:(紅色部份為<dspace>標籤之外新增的程式碼)

  String action = (String) UIUtil.getOriginalURL(request);
if (action.indexOf("action=json") != -1)
{
   JSPManager.showJSP(request, response,
                    "/submit/upload-file-json.jsp");
}
else
{
%>

<dspace:layout locbar="off" navbar="off" titlekey="jsp.submit.get-file-format.title" nocache="true">

……

</dspace:layout>
<%
}  
%>
/upload-file-json.jsp

上面的修改之後會轉到這個網頁,這是我另外撰寫的程式。該程式會去取得最新上傳的檔案(專門指bitstream),回傳filename、url、format等參數。程式也很簡單,只有22行,原始碼如下:

<%@ page contentType="text/html; charset=utf-8" language="java" import="java.sql.*" errorPage="" %>
<%@ page import="org.dspace.core.Context" %>
<%@ page import="org.dspace.app.webui.servlet.SubmissionController" %>
<%@ page import="org.dspace.app.util.SubmissionInfo" %>
<%@ page import="org.dspace.content.Bitstream" %>
<%@ page import="org.dspace.app.webui.util.UIUtil" %>
<%
    // Obtain DSpace context
    Context context = UIUtil.obtainContext(request);    

  //get submission information object
    SubmissionInfo subInfo = SubmissionController.getSubmissionInfo(context, request);

Bitstream[] bitstreams = subInfo.getSubmissionItem().getItem().getNonInternalBitstreams();
Bitstream json_bitsream = bitstreams[0];  //bitstreams[(bitstreams.length-1)];  
%>
{
  error: '',
  filename: '<%= bitstreams[(bitstreams.length-1)].getName() %>',
  url: '<%= request.getContextPath() %>/retrieve/<%= bitstreams[(bitstreams.length-1)].getID() %>/<%= org.dspace.app.webui.util.UIUtil.encodeBitstreamName(bitstreams[(bitstreams.length-1)].getName()) %>',
  format: '<%= bitstreams[(bitstreams.length-1)].getFormatDescription() %>'
}

[dspace-jspui-webapp]/display-item.jsp (display-item.20081015.zip)

image

這是顯示item的頁面。我希望顯示的值能夠動態地調整成指定的樣式,而不是只有文字。像是XMLMetadata我希望能以樹狀結構呈現、fileupload則是呈現檔名。

修改1:引用JavaScript跟CSS檔案。作法同edit-metdata.jsp的修改1,程式碼如下:(紅色部份為新增的程式碼)

<dspace:layout title="<%= title %>">

<%
    if (handle != null)
    {
%>

<script type="text/javascript" src="<%= request.getContextPath() %>/extension/jquery.js"></script>
<script type="text/javascript" src="<%= request.getContextPath() %>/extension/display-item/metadata-value-display.js"></script>
<script type="text/javascript" src="<%= request.getContextPath() %>/extension/fckeditor/fckeditor.js"></script>
<script type="text/javascript" src="<%= request.getContextPath() %>/extension/ajaxfileupload/ajaxfileupload.js"></script>
<script type="text/javascript" src="<%= request.getContextPath() %>/extension/ajaxfileupload/ajaxfileupload_fn.js"></script>
<script type="text/javascript" src="<%= request.getContextPath() %>/extension/ajaxfileupload/ajaxfileupload_fnXML.js"></script>
<script type="text/javascript" src="<%= request.getContextPath() %>/extension/fckeditor/fckeditor_display_toggle.js"></script>
<script type="text/javascript" src="<%= request.getContextPath() %>/extension/xmlmetadata/dspace-inputtype-xml.js"></script>
<script type="text/javascript" src="<%= request.getContextPath() %>/extension/xmlmetadata/ui.datepicker.js"></script>
<link rel="stylesheet" href="<%= request.getContextPath() %>/extension/xmlmetadata/flora.datepicker.css" type="text/css" media="screen" title="Flora (Default)">
<link rel="stylesheet" href="<%= request.getContextPath() %>/extension/xmlmetadata/dspace-inputtype-xml.css" type="text/css" media="screen">



    <table align="center" class="miscTable">

修改2:呼叫metadataValueDisplay()。全部網頁顯示之後的最後,呼叫metadataValueDisplay()以動態調整內容的顯示。程式碼如下:(紅色的部份為新增的程式碼)

<%
    }
%>
    <p class="submitFormHelp"><fmt:message key="jsp.display-item.copyright"/></p>
  <script type="text/javascript">
  metadataValueDisplay("<%= request.getContextPath() %>");
  </script>

</dspace:layout>

老實說,這並不是很好的作法。建議能從JSP顯示的時候就能呈現檔案或XML的樹狀結構,而不是等網頁讀出來再用JavaScript動態修改。但這部份程式得花另一番功夫了。


[dspace-api]/...... (dspace-api.20081015.zip)

API的三個檔案,不小心改錯的話很容易讓整個DSpace都毀掉喔。

/submit/step/DescribeStep.java

此程式會依照input-type來決定資料的儲存方式,由於我新增的三種input-type都是跟著onebox、textarea來跑的,所以就跟他們一樣的設定即可。程式碼如下:(紅色部份為新增的程式)

            else if ((inputType.equals("onebox"))
                    || (inputType.equals("twobox"))
                    || (inputType.equals("textarea"))
          || (inputType.equals("texteditor"))
          || (inputType.equals("xmlmetadata"))
          || (inputType.equals("fileupload")))

            {
                readText(request, item, schema, element, qualifier, inputs[j]
                        .getRepeatable(), "en");
            }
/app/util/DCInput.java

DSpace並沒有預設值的功能,item template文件樣板只是預設該collection底下的item會擁有固定的值,但這跟我們一般認知的預設值:「每次新增欄位時,某些值是被預先載入的」有段差異,尤其是XMLMetadata是將表單的形式預先寫入才能運作,因此我們要幫他新增取得default-value的功能。

修改1:宣告內用變數defaultValue:(紅色部份是新增的程式碼)

    /** is the entry closed to vocabulary terms? */
    private boolean closedVocabulary = false;
    
    /**  for XMLMetadata  */
    private String defaultValue = null;

修改2:用get取得裡面的資料:(紅色部份是新增的程式碼)

        vocabulary = (String) fieldMap.get("vocabulary");
        //for XMLMetadata
        defaultValue = (String) fieldMap.get("default-value");

        String closedVocabularyStr = (String) fieldMap.get("closedVocabulary");

修改3:宣告公用函式getDefaultValue():(紅色部份是新增的程式碼)

  public boolean isClosedVocabulary() {
    return closedVocabulary;
  }
  
  /** for XMLMetadata */
  public String getDefaultValue()
    {
        return defaultValue;
    }

/core/Utils.java

原本DSpace在display item的頁面中,如果你存的資料裡面有用到一些特殊字元的時候,會被轉換成顯示於HTML的脫逸字元(Escape characters,都會以「&」開頭、「;」結尾),以避免這些可能是HTML程式碼的字元影響版面呈現或程式運作。這種脫逸字元跟C、Java、PHP等會用反斜線「\」來脫逸的方式不同,是HTML獨特的編碼。

在DSpace當中,將會被脫逸掉的字元如下:

  • 「&」→「&amp;」
  • 「"」→「&quot;」
  • 「<」→「&lt;」
  • 「>」→「&gt;」」

※HTML的拖曳字元,可以參考網頁:HTML Escape Characters: Complete List

由於我們用到了由FCKeditor轉換而來的HTML,也會使用XMLMetdata來儲存、顯示資料,我們就不能讓他把這些字元脫逸。接著我們就是addEntities()的內容,程式碼如下:(紅色表示註解掉字元脫逸的程式)

    public static String addEntities(String value)
    {
        if (value==null || value.length() == 0)
            return value;
        
        //value = value.replaceAll("&", "&");
        //value = value.replaceAll("\"", """);

        // actually, &apos; is an XML entity, not in HTML.
        // that's why it's commented out.
        // value = value.replaceAll("'", "&apos;");
        //value = value.replaceAll("<", "<");
        //value = value.replaceAll(">", ">");


        return value;
    }

由於存在同名字檔案在/app/util/Utils.java,千萬要小心不要覆蓋錯囉!


[dspace]/config/...... (config.20081015.zip)

調整DSpace的設定。

/input-forms.dtd

此文件定義了input-forms.xml的格式,我們在此處加上default-value的宣告。

修改1:宣告field裡面的default-value是可選擇性的:(紅色部份為新增的程式碼)

<!ATTLIST page number NMTOKEN #REQUIRED>
<!ELEMENT field (dc-schema, dc-element, dc-qualifier?, repeatable?, label, input-type, hint, required?, vocabulary?, default-value?) >
<!ELEMENT dc-schema (#PCDATA) >

修改2:宣告default-value這個元素:(紅色部份為新增的程式碼)

<!ELEMENT form-value-pairs (value-pairs)* >
<!ELEMENT value-pairs (pair)+ >

<!ELEMENT default-value (#PCDATA) >
/item-submission.xml

此處調整敘述階段(describe)跟上傳階段(upload)的順序。原本的順序如下

  1. 取得開始的資訊:他會問你一些問題,像是是否要上傳多個檔案、是否擁有多個標題之類的,你要打勾來決定。
  2. 敘述資料:填寫由input-forms.xml規定好的表單。
  3. 上傳檔案:上傳bitstream檔案。DSpace的設計理念是前面的敘述階段在敘述這個檔案,例如論文的作者、出版資訊等等。預設此階段是一定上傳資料,不過可以在dspace.cfg裡面取消這個設定。
  4. 確認資料:網頁會列出你之前填寫及上傳的資訊,並讓你回頭去修正。
  5. 授權條款的確認

修改:敘述階段與上傳階段互換。如果要在敘述階段上傳檔案,即使我們在上傳階段並不上傳資料,還是必須先經過上傳階段才行。因此我們將敘述階段跟上傳階段調換過來,程式碼如下:(紅色部份為兩個互換的階段)

     <!--Step 1 will be to gather initial information-->
    <step>
      <heading>submit.progressbar.initial-questions</heading>
    <processing-class>org.dspace.submit.step.InitialQuestionsStep</processing-class>
    <jspui-binding>org.dspace.app.webui.submit.step.JSPInitialQuestionsStep</jspui-binding>         
    <xmlui-binding>org.dspace.app.xmlui.aspect.submission.submit.InitialQuestionsStep</xmlui-binding>
      <workflow-editable>true</workflow-editable>
    </step>  

      <!--Step 2 will be to Upload the item-->
      <step>
        <heading>submit.progressbar.upload</heading>
        <processing-class>org.dspace.submit.step.UploadStep</processing-class>
        <jspui-binding>org.dspace.app.webui.submit.step.JSPUploadStep</jspui-binding>
    <xmlui-binding>org.dspace.app.xmlui.aspect.submission.submit.UploadStep</xmlui-binding>
        <workflow-editable>true</workflow-editable>
      </step>
          
      <!--Step 3 will be to Describe the item.-->
      <step>
        <heading>submit.progressbar.describe</heading>
        <processing-class>org.dspace.submit.step.DescribeStep</processing-class>
        <jspui-binding>org.dspace.app.webui.submit.step.JSPDescribeStep</jspui-binding>
    <xmlui-binding>org.dspace.app.xmlui.aspect.submission.submit.DescribeStep</xmlui-binding>
        <workflow-editable>true</workflow-editable>
      </step>
   

    <!--Step 4 will be to Verify/Review everything -->
    <step>
        <heading>submit.progressbar.verify</heading>
        <processing-class>org.dspace.submit.step.VerifyStep</processing-class>
      <jspui-binding>org.dspace.app.webui.submit.step.JSPVerifyStep</jspui-binding>
    <xmlui-binding>org.dspace.app.xmlui.aspect.submission.submit.ReviewStep</xmlui-binding>
    <workflow-editable>true</workflow-editable>
      </step>
/dspace.cfg
此處有很多DSpace的設定,也包含了是否限制一定要上傳檔案的設定。修改的程式碼如下:(紅色部份為新增的設定) ##### Settings for Submission Process #####

# Should the submit UI block submissions marked as theses?
webui.submit.blocktheses = false

# Whether or not we REQUIRE that a file be uploaded
# during the 'Upload' step in the submission process
# Defaults to true; If set to 'false', submitter has option to skip upload
#webui.submit.upload.required = true
webui.submit.upload.required = false


DSpace操作步驟說明

以上修改完畢之後,還需要進行重新安裝的步驟。

DSpace 1.5版重新安裝:出處在說明文件DSpace System Documentation: Configuration and Customization ([dspace-source]/dspace/docs/configure.html#jspui,dspace.1.5.0.configure.zip),大致上步驟如下:

  1. 進入指令模式
  2. 移動到你的[dspace-source]/dspace/目錄
    > cd [dspace-source]/dspace/
  3. 執行ant編譯
    > mvn package
  4. 移動到你的[dspace-source]/dspace/target/dspace-[version].dir目錄
    > [dspace-source]/dspace/target/dspace-[version].dir
  5. 執行ant打包
    > ant -Dconfig=[dspace]/config/dspace.cfg update
  6. 停止你的Tomcat伺服器
    > cd [tomcat]/bin/
    > ./shutdown.sh
  7. 複製新的.war檔案到Tomcat的webpapps目錄:
    > \cp -rf [dspace]/webapps/* [tomcat]/webapps/
  8. 重新啟動Tomcat伺服器
    > cd [tomcat]/bin/
    > ./startup.sh


參考資料


本來想要一篇之內寫完的,沒想到內容已經長到令人看不下去的地步,連Windows Live Writer連帶的都變得很慢,這就是警告我該換一篇寫了。繼續努力!

(more...)