2016年10月3日

[Mac] 設定 Mac 通訊錄讀取 Zimbra 全域聯絡人(更新 macOS Sierra)

Step 1. 聯絡資訊 --> 帳號...


Step 2. 用「加入其他帳號 ...」來新增帳號

Step 3. 選擇 CardDAV 帳號


Step 4. 這邊的伺服器路徑設成: /principals/users/galsync/




Step 5.  前項步驟完成後,回到「聯絡人資訊」,再從「偏好設定」進入。


Step 6. 這邊的伺服器路徑改成「/dav/galsync」





2016年6月4日

[.Net] System.Net.Mail 寄信到 Zimbra 主旨亂碼的問題


如下圖,主旨太長就被截斷
根據 RFC2047 上講的
An 'encoded-word' may not be more than 75 characters long
看樣子要嘛就是 Zimbra 處理的時候有問題,要嘛就是 System.Net.Mail 發信的時候處理有問題。

顯示信件原始內容來看,直接拿 subject 裡最後一行去解 Base64 會發現有亂碼!
解回來的內容開頭是亂碼:

問題追到這邊大概有答案了,就是 System.Net.Mail 雖然會幫我們處理不能超過 75 個字元的問題,但是沒有考慮到雙位元的文字處理,而 Zimbra 分段去處理主旨的編碼,就會造成中文字被分割成兩個半的問題。

所以追一下 System.Net.Mail 的 Source Code 會發現,如果自己處理掉編碼換行這段,System.Net.Mail 就會以我們處理過的原封不動丟出去。

雖然這邊註解是說會 re-encode before sending ,但是再往 DecodeHeaderValue 裡面看,會發現,他只處理 Base64 編碼過的,如果是又做過 75 個字元切段又做 Base64 的話,他就會直接放棄 re-encode 。

用 ? 去切字串,如果切出來不是  5 個,就放棄。( MimeBasePart.cs )

所以,如果我們自己處理有考慮中文字的 75 個字元切段後再 Base64 編碼的話,應該就可以解決這個問題。

private string folding(string input)
        {
            string tempInput = input;
            string result = "";
            const int f = 37; // 一個字一律算兩個 bytes 最長只能 75 所以 75/2 https://www.ietf.org/rfc/rfc2047.txt
            for (int i = 0, len = tempInput.Length; i <= len; i += f)
            {
                string x = tempInput.Substring(i, (i + f >= len ? len - i : f));
                result += string.Format(@"=?UTF-8?B?{0}?=", Convert.ToBase64String(Encoding.UTF8.GetBytes(x)));
            }
            if (result.Split("?".ToCharArray()).Length == 5) result = result + "=?UTF-8?B??="; // 只切到一次還會會被解回去,所以補一個空的
            return result;
        }

2013年3月29日

[Visual Studio 2012 Express] 開啟 *.config 檔案時,發生 "Value does not fall within the expected range" 的決解方式

是情是這樣發生的,
我有一個自己測試用的專案,放在 SkyDrive 自動同步的目錄中,
在我的兩台電腦中,他很方便,可以自動的幫我同步,
有一天,我改完程式,把 Notebook 蓋起來直接下班,
 Notebook 打開後,發現他當機重開,
之後再開起這個專案裡的 *.config 檔的時候,他就一直賞我下面這個訊息。










關鍵字搜尋了一下,
都是說要還原 Visual Studio 的設定,但,這不合理!
因為我別的專案是正常的,只有這個怪怪的。

後來找到有人有提供的解決方法:
http://forums.asp.net/post/5138757.aspx

沒錯!就是砍掉 *.suo 這個隱藏檔就好了。
這個檔案會儲存上次開啟專案時的一些狀態,
例如中斷點的設定,開了哪些程式 ... 等等。
推測,應該是因為不正常關機,造成 *.suo 損壞所導致的。


很多時候,刪掉 *.suo 、 *.user 可以決解一些問題,
所以我一向都建議不要把這後副檔名的檔案 commit 到版本控管的 Server 上。



2013年3月21日

[.Net] 決解 Visual Studio 2012 Express NuGet 無法載入的問題

不知道有沒有人遇過這樣的問題,反正我是遇到了 Orz
安裝 Visual Studio 2012 Express 一切正常,
但是,
開起已經有使用 NuGet 的專案時,出現:
未正確載入套件 'NuGet.Tools.NuGetPackage, NuGet.Tools ......'











加入一個新的專案有用到 NuGet 的時候會出現:
這個範本嘗試載入元組件 'NuGet.VisualStudio.Interop ......'








以上是發病的症狀,
我安裝的環境是 Windows Server 2012 Evaluation 英文版,
加 Visual Studio 2012 Express (Web、Desktop、Phone SDK),
不管我怎麼移除再重新加入都沒用,






後來就手動在 command line 下執行
vsixinstaller /uninstall:NuPackToolsVsix.Microsoft.67e54e40-0ae3-42c5-a949-fddf5739e7a5
執行完成後會出現以下的畫面:












咦 ~ 一次移除三個,推想這案情不單純,因為之前移除的時候都只有針對 for Web 的版本做的,我猜有可能是因為安裝時,不知道什麼原因,造成他們彼此之間不一致所導致的問題。

"NuPackToolsVsix.Microsoft.67e54e40-0ae3-42c5-a949-fddf5739e7a5"
這串 ID 可以從:

C:\Program Files (x86)\NuGet\Visual Studio 2012\NuGet.Tools.vsix

這個檔案裡面取得,先把 NuGet.Tools.vsix 的副檔名改成 .zip 再解壓縮後,
裡面的 extension.vsixmanifest 檔案中可以看到






最後,要再把 NuGet.Tools.vsix 安裝回來,
只要在 Command Line 下,上述的目錄中執行以下命令即可。
vsixinstaller NuGet.Tools.vsix

2012年12月3日

[iCalendar] 如何用 .ics 檔預約或更新 Outlook 行事曆

細節要參考 RFC2445, 2446, 2447 關於 VEVENT 的設定

RFC 2446:

iCalendar Transport-Independent Interoperability Protocol (iTIP)
specifies how calendaring systems use iCalendar objects
to interoperate with other calendar systems. It does so in a general
way so as to allow multiple methods of communication between systems.
Subsequent documents specify interoperable methods of communications
between systems that use this protocol.

iTIP 在描述的是行事曆與行事曆之間利用 iCalendar objects 的溝通方式,
舉例:
A 行事曆的 User PUBLISH 一個 VEVENT 給 B 行事曆的某個 User。
A 行事曆的 User REQUEST 一個 VEVENT 給 B 行事曆的某個 User 更新先前 PUBLISH 的 VEVENT。
B 行事曆的 User COUNTER (改變的建議)一個 VEVENT 給 A 行事曆的召集人。

而這邊的需求比較簡單,我們只需要統一的一個召集人,發出會通知(或更新)給所有參與人即可,
所以只需要用到 VEVENT REQUEST ,

一個 VEVENT REQUEST 的 .ics 格式如下:

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//RDU Software//NONSGML HandCal//EN
METHOD:REQUEST
BEGIN:VEVENT
DESCRIPTION:描述寫這邊
DTSTART:20121225T080000
DTEND:20121225T170000
DTSTAMP:20121129T093257
SEQUENCE:1
SUMMARY:標題寫這邊
UID:guid
STATUS:CONFIRMED
ORGANIZER:mailto:your@mail
END:VEVENT
END:VCALENDAR


基本上如果只要發佈(PUBLISH),就不需要用 ORGANIZER(rfc2446上說需要,但測試結果可有可無),
但 PUBLISH 不能更新原有的行事曆,
如果要更新就要用 REQUEST ,用 REQUEST 就要有 ORGANIZER。
要更新一個原有的行事曆,
必須要注意以下幾點:
1. UID:唯一識別代碼,前者和後者要一致
2. DTSTAMP: 產生 ics 的時間後者要比前者大
3. SEQUENCE: 要遞增,一樣後者要比前者大( SEQUENCE 從 1 開始,往後遞增)
4. ORGANIZER: 要填入符合 email 格式的內容

以上四點在 microsoft outlook 2007 中,2 和 3 有檢查後者一定要比前者大,
第 4 項貝沒有檢查一定要一樣(因為可以變更召集人),
但是一定要有 email 格式的內容,
這裡要注意召集人如果是自己(沒有設召集人,outlook預設是自己)的話,
就沒辦法更新( 這個超重要,試了很久才試出來 XD )。



METHOD 的部份定義在: http://tools.ietf.org/html/rfc2446#page-7

The ITIP methods
Method Description
PUBLISH Used to publish a calendar entry to one or more Calendar Users. There is no interactivity between the publisher and any other calendar user. An example might include a baseball team publishing its schedule to the public.
REQUEST Used to schedule a calendar entry with other Calendar Users. Requests are interactive in that they require the receiver to respond using the Reply methods. Meeting Requests, Busy Time requests and the assignment of VTODOs to other Calendar Users are all examples. Requests are also used by the "Organizer" to update the status of a calendar entry.
REPLY A Reply is used in response to a Request to convey "Attendee" status to the "Organizer". Replies are commonly used to respond to meeting and task requests.
ADD Add one or more instances to an existing VEVENT, VTODO, or VJOURNAL.
CANCEL Cancel one or more instances of an existing VEVENT, VTODO, or VJOURNAL.
REFRESH The Refresh method is used by an "Attendee" to request the latest version of a calendar entry.
COUNTER The Counter method is used by an "Attendee" to negotiate a change in the calendar entry. Examples include the request to change a proposed Event time or change the due date for a VTODO.
DECLINECOUNTER Used by the "Organizer" to decline the proposed counter-proprosal.



另外關於 content type ,截錄一下rfc2447的說明:

2.4 Content Type
   A MIME body part containing content information that conforms to this
   document MUST have an [RFC-2045] "Content-Type" value of
   "text/calendar". The [RFC-2045] "Content-Type" header field must also
   include the type parameter "method". The value MUST be the same as
   the value of the "METHOD" calendar property within the iCalendar
   object.  This means that a MIME message containing multiple iCalendar
   objects with different method values must be further encapsulated
   with a "multipart/mixed" MIME entity. This will allow each of the
   iCalendar objects to be encapsulated within their own "text/calendar"
   MIME entity.


2012年11月8日

[IIS] URL Rewrite 設定備忘

備忘:
我有一個網站,網站內容是純 html、js 組成的,但 js 會呼叫 services 做事,
我們希望 services 可以被 hosted 在別的 server 上,但我們同時也希望,
js 裡面在寫呼叫 services 的事,不需要管 services 的 server 在哪裡。
於是我們在這個網站上,設了一個反向 Proxy 做 URLRewrite ,
把特定的路徑 request 都導向另一台 Server 。

^s/(.*) 表示 s 這個目錄所以有網址都要 rewrite 。
("^"是指一開始,因為後面馬上接 "/",所以就是 "s" 這個目錄,

圖二中 {R:1} 表示上面表示式中 "(.*)" match 到的字串,{R:0}則表示含 "s/"。
(圖一)
(圖二)

參考資料:http://www.microsoft.com/taiwan/technet/iis/expand/URLRewrite.aspx



2012年10月9日

[EasyFlow] 增加TipTop XML 傳來之欄位


原因:
   TipTop 的請購單,備註是放在單身中,USER 反映簽核時不夠明顯,也無法輸入太多字

改善:
1.在 TipTop APMT420 中的自訂欄位,新增一欄備註( pmkud01),設定此欄位要傳到 EasyFlow

2.打開此表單的 EasyFlow XML檔 (路徑 EFNET \ WS \ EFNETService \ TTXMLDoc \ Draw \ apmt420.xml )
       在<Grid name="gr5261" width="52" height="7">的最後插入
     
<Label text="備註" posY="0" posX="69" gridWidth="8" sizePolicy="dynamic"/>
<FormField name="pmk_file.pmkud01" colName="pmkud01" sqlType="VARCHAR(255)" fieldId="999" sqlTabName="pmk_file"
      tabIndex="999">
<ButtonEdit width="10" action="controlp" image="zoom" posY="0" posX="81" gridWidth="12" scroll="1"
      sample="MMMMMM000000" comment="備註[pmkud01]"/>
</FormField>


3. 修改此EasyFlow表單的HTML(路徑 EFNET \ src \ TEI \ TEIAPMT420)
         在TABLE的最下面新增

<tr>
<td width="90" id="pmkud01_1" nowrap="true" align="right" class="normalFormCaption">備註</td><td nowrap="true" width="100">
<table style="margin:0px;padding:0px;" border="0" cellspacing="0" cellpadding="0">
<tr>
<td><span id="pmkud01_2" class="normalSpan"></span></td>
</tr>
</table>
</td>
</tr>

成果:



[EasyFlow] DropDownList 下拉式選單的加工

加工一:在SQL Command加上其他較複雜的SQL語法

原因:
      因為在 EasyFlow 表單設計中的DropDownList選單中的 SQL Command 無法用 SQL 的
      ORDER BY  or HAVING 之類的語法, 所以必須手動加在 CS 檔中.
作法:  
      找到 ” #region 重新取得 select selectXXXXX下拉選項 ”,修改 string tSQL 後面的  SQL 語法



#region 重新取得selectcategory2下拉選項 
[AjaxPro.AjaxMethod(AjaxPro.HttpSessionStateRequirement.ReadWrite)]  
public string ajaxGetselectcategory2Item(string pselectcategory1Val)  
{  
 string strReturn = ""; string tSQL = @"select distinct * from LMIT where FormCode='表單名稱' 
        and ItemNo='1' and Category=N'" +  pselectcategory1Val + "' order by 00";
 strReturn = createEasyFlowDataTable(tSQL, "Details", "Details");  
 return strReturn;  
}  
#endregion 重新取得selectcategory2下拉選項 

加工二:DropDownList 第一個選項是空白

作法:   
  要刪除這個空白的話,在if (!IsPostBack)中,找到這個下拉式選單,註解掉this.selectXXXXX.Items.Insert(0, new ListItem("", ""));
  在JS檔中的FunOnChange_ selectXXXXX 註解掉CreateOption("","請選擇", selectXXXXX DDL);即可

2012年10月8日

[EasyFlow] 自製 TipTop 放行單



原因:
         在 TipTop AXM520 中,沒有信用查核放行的單子可拋轉至 EasyFlow 簽核,且因 EasyFlow 版本為 .net ,鼎新將 CS 檔包成 DLL 檔了,沒有 Code ,故無法在出貨單拋轉至 EasyFlow 簽核完成時回寫 TipTop .
             
 故現行公司的做法是:
         需要放行時由相關人員填寫紙本的信用查核放行單,依核決權限簽核後,交由會計至下面欄位勾選允許放行.

流程改善:
1. 當需要放行時,負責的人員至 EasyFlow 開立信用查詢放行單,依核決權限簽核,
         當簽核完成後,更新 TipTop DB OGA903 欄位(改為Y)





    防呆:
a)送出前檢查: 須為出貨單 / 確認碼不為X:作廢 / 狀況碼不為9:作廢 / 信用額度查核放行不為Y / Gridview裡面的單號須為同一客戶且幣別相同
b)簽核完成後檢查:流程中是否有會計部門的員工
        c)輸入出貨單單號自動帶出該訂單之金額
3.    <!--[endif]-->檢查後帶出相關資料:
a)客戶編號 / 客戶名稱 / 授信條件 / 付款條件 / 幣別 / 本幣金額
b)計算出原幣金額(單價*數量*匯率)
c)檢查通過才會出現送出的按鈕,檢查不通過會跳出視窗題是哪邊錯誤


#region 簽核流程結束時要處理的事情
protected override void AfterApprove()
{
if (this.BlnCaseClosed && this.AryFlowProperty.SerialSignResult == "2")
{
   //先判斷流程中是否有財會部的人
   string sql_EFdept = @"select * from resdd as D
        where resdd001='表單TABLE' and resdd002='"+this.StrSheetNo+"'
              and exists (
                select '"+"x"+"' from  resak X where X.resak001 =
                    D.resdd008 and (X.resak015 = 'FN3000')
             )";
DataSet set_EFdept = this.m_objDB.CreateRecordset(" use EFNETDB;" +
                   sql_EFdept, this.Session["strProcID"].ToString());
  DataTable temp_EFdept = set_EFdept.Tables[0];
  if (temp_EFdept.Rows.Count == 0)
  {
    //把ERROR存成TXT檔
  }
  else
  {
    StringBuilder builder = new StringBuilder();
    builder.Append(" use EFNETDB;");
    builder.Append(" select saleno ");
    builder.Append(" FROM 表單TABLEb ");
    //表單代號,表單編號
    builder.AppendFormat(" WHERE 表單TABLEb001='{0}' ", this.formID);
    builder.AppendFormat(" AND 表單TABLEb002='{0}' ", this.StrSheetNo);
    DataSet set_saleno=this.m_objDB.CreateRecordset(builder.ToString(),this.Session["strProcID"].ToString());
    DataTable temp_saleno = set_saleno.Tables[0];
    foreach (DataRow saleno_read in temp_saleno.Rows)
    {
       string sql_OGAFILE = @"UPDATE OPENQUERY(ORACLE,'
               select oga903 from TT.oga_file where oga01=''" + saleno_read["saleno"] + "''') SET OGA903='Y'";                      
this.m_objDB.ExecuteSQL(sql_OGAFILE.ToString(),this.Session["strProcID"].ToString());
     }
   }
 }





#region 按下檢查時要做的事情

protected void butcheck_Click(object sender, EventArgs e)
{
    //先清空textbox
    
    
    base.BtnCreateToolSendForm.Attributes.Add("style", "display:none");//隱藏送出的按鈕
    int rows_count = MasterObj.DetailObjs[0].NewRows.Count;//取得grid有幾列資料
    System.Text.StringBuilder sb = new System.Text.StringBuilder();
    string customer_1 = "";//客戶名稱
    string customer_no = "";//客戶編號
    string terms_payment = "";//付款條件
    int customer_i = 1;
    string currency_1 = "";//幣別
    double local_currency = 0.0;//本幣未稅金額NTD
    double org_currency = 0.0;//原幣未稅金額
    double credit = 0.0; //授信額度
    double rate = 0.0; //匯率
    string txt_saleno = "";//銷貨單號
    bool bPass = true;
    if (rows_count == 0)
    {
         MessageBox.Show("請先輸入訂單編號");
    }
    else
    {
        foreach (DscRow dscRow in MasterObj.DetailObjs[0].NewRows)
        {
            string sqlstring_TT = @"Select * From OPENQUERY(ORACLE, '
            select oga01 as 銷貨單號,oga03 as 客戶編號,oga032 as 客戶名稱,oga903 as 是否放行,oga55 as 狀況碼,
                    ogaconf as 核准碼,oga09 as 單據別,oag02 as 付款條件,oga24 as 匯率,
                    oga50 as 出貨金額,OCCG03 as 授信額度,oga23 as  幣別,oga50 as 原幣未稅金額,(
                              select sum(omb16) from TT.omb_file  
                                    where TT.oga_file.oga01=omb31
                            ) as 本幣未稅金額
            from TT.oga_file join TT.oag_file on oga32=oag01
                 join TT.occg_file on OCCG01=oga03
            where oga01=''" + dscRow["saleno"].Value.ToString() + "''')";

           DataSet set_TT = this.m_objDB.CreateRecordset(" use EFNETDB;" +sqlstring_TT,this.Session["strProcID"].ToString());
           DataTable dtTemp_TT = set_TT.Tables[0];
           if (dtTemp_TT.Rows.Count == 0)
           {
               sb.Append("查無'" + dscRow["saleno"].Value.ToString() + "'的訂單資料,請檢查訂單號碼是否錯誤\r\n");
                bPass = false;
          }
          else
          {
              foreach (DataRow s_read in dtTemp_TT.Rows)
              {
                if (s_read["單據別"].ToString().Trim() == "1")
                {
                  sb.Append("單號:" + s_read["銷貨單號"] + "是出貨通知單喔~請再確認\r\n");
                  bPass = false;         
                }
                else if (s_read["核准碼"].ToString().Trim()=="X")
                {
                  sb.Append("單號:" + s_read["銷貨單號"] + "核准碼是作廢'X',請再確認");
                  bPass = false;
                }
                else if (s_read["是否放行"].ToString().Trim()!= "N")
                {
                  sb.Append("單號:" + s_read["銷貨單號"] + "已放行,請再確認.\r\n");
                  bPass = false;
                }
                else
                {
                  if (customer_i == 1)
                {
                customer_1 = s_read["客戶名稱"].ToString();
                currency_1 = s_read["幣別"].ToString();
                double x = 0.0;
                bool flag_1 = double.TryParse(s_read["匯率"].ToString(), out x);
                bool flag_2 = double.TryParse(s_read["原幣未稅金額"].ToString(), out x);
                bool flag_3 = double.TryParse(s_read["授信額度"].ToString(), out x);
                if (flag_1 && flag_2 && flag_3)
                {
                   rate = double.Parse(s_read["匯率"].ToString());
                   org_currency = double.Parse(s_read["原幣未稅金額"].ToString());
                   credit = double.Parse(s_read["授信額度"].ToString());
                }
                customer_no = s_read["客戶編號"].ToString();
                terms_payment = s_read["付款條件"].ToString();
                local_currency = rate * org_currency;
                txt_saleno = s_read["銷貨單號"].ToString();
              }
              else
              {
               if (customer_1 != s_read["客戶名稱"].ToString())               {
                 sb.Append("單號:" + s_read["銷貨單號"] + "公司名稱與其他不同");
                 bPass = false;
               }
               if (currency_1 != s_read["幣別"].ToString())
               {
                 sb.Append("單號:" + s_read["銷貨單號"] + "幣別與其他不同.\r\n");
                 bPass = false;
               }
               if (txt_saleno == s_read["銷貨單號"].ToString())
               {
                 sb.Append("單號:" + s_read["銷貨單號"] + "重複.\r\n");
                 bPass = false;
               }
               org_currency = org_currency + (double.Parse(s_read["原幣未稅金額"].ToString()));
               local_currency = local_currency + (rate * (double.Parse(s_read["原幣未稅金額"].ToString())));
                            }
                        }
                    }
                }
                customer_i++;
            }
        }
string ErrorMsg = sb.ToString();
if (bPass && butcheck.ToolTip != "0")
{
  //Lucas 觸發 refresh 重新解悉流程
  string str_java = "document.getElementById('" +base.BtnCreateToolRefresh.ClientID + "').click();";
  Page.ClientScript.RegisterStartupScript(typeof(string), "jva", str_java, true);
  base.BtnCreateToolSendForm.Attributes.Add("style", "display");
  butcheck.ToolTip = "0";
  butcheck.Text = "已檢查";
  currencymoney.Text = org_currency.ToString();//原幣未稅金額
  orgmoney.Text = local_currency.ToString();
  customerno.Text = customer_no;
  overdue3.Text = terms_payment;
  credit3.Text = credit.ToString();
  }
  else
  {
     base.BtnCreateToolSendForm.Attributes.Add("style", "display:none");
     butcheck.ToolTip = "";
     MessageBox.Show("錯誤訊息如下:\r\n" + ErrorMsg);
  }
} 
#endregion
}

2012年9月20日

[.Net] 如何用 C# 將 LibreOffice 的 ods 轉成 excel 的 xls

首先,當然是要安裝 LibreOffice
接下來要安裝 LiberOffice SDK ,坦白說,這個步驟我找好久,
下圖是下載的位置:


安裝完成之後,開啟 dotNet C# 專案,將 C:\Program files\LibreOffice 3.6\sdk\cli 中的 Dll 全部加進參考。


接下來就可以開始寫程式了:
public static string FilePath2Url(string fname)
{
    return string.Format("file:///{0}", fname.Replace("\\", "/"));
}

internal static XComponent openCalcSheet(string fname)
{
    string url = FilePath2Url(fname);
    XComponentContext oStrap = uno.util.Bootstrap.bootstrap();
    XMultiServiceFactory oServMan = (XMultiServiceFactory)oStrap.getServiceManager();
    XComponentLoader desktop = (XComponentLoader)oServMan.createInstance("com.sun.star.frame.Desktop");
    PropertyValue[] loadProps = new PropertyValue[1];
    loadProps[0] = new PropertyValue();
    loadProps[0].Name = "Hidden";
    loadProps[0].Value = new uno.Any(true);
    XComponent document = desktop.loadComponentFromURL(url, "_blank", 0, loadProps);
    return document;
}

public static XSpreadsheetDocument Open(string fname)
{
    return (XSpreadsheetDocument)openCalcSheet(fname);
}

public static void Save(XSpreadsheetDocument xDoc, string fname)
{
    string url = FilePath2Url(fname);
    PropertyValue[] propVals = new PropertyValue[2];
    /* 另存成 MS Excel 97 的格式,
       格式可以參考:http://www.oooforum.org/forum/viewtopic.phtml?t=3549  (找不到官方的清單 Orz,看官知道在哪的,請通知一聲,感恩ㄚ)
       原理可以參考:http://wiki.openoffice.org/wiki/Documentation/DevGuide/OfficeDev/Integrating_Import_and_Export_Filters
     */
    propVals[0] = new PropertyValue { Name = "FilterName", Value = new uno.Any("MS Excel 97") };
    propVals[1] = new PropertyValue { Name = "Overwrite", Value = new uno.Any(true) };
    ((XStorable)xDoc).storeToURL(url, propVals);
}

public static void Convert2Xls(string fname, string new_fname)
{
    Save(Open(fname), new_fname);
}
於是只要呼叫 Convert2Xls(ods檔案位置, xls輸出檔案位置) 就可以將 ods 轉成 xls 囉。

範例程式:將選到的 ODS 另存成 XLS 並透過 oledb 讀取資料顯示在 DataGridView 上

參考資料:
http://www.oooforum.org/forum/viewtopic.phtml?t=3549
Integrating Import and Export Filters
use openoffice uno cli with c sharp to create a spreadsheet
c-cpp.r3dcode.com


2012年9月17日

[Refactoring] Demo video


原本這程式會分別出兩張報表,新的需求是要併成一張,
看到程式的第一個念頭就是「重構」,
請記住!「重構」不是「重寫」,
我們是懷著讓原本程式更容易閱讀的心情再做「重構」的。

影片不是很清楚,程式不是很容易閱讀,純粹紀錄「重構」的感覺,
如果你也進行過「重構」工作,我想你會懂我的。




2012年8月21日

[Javascript] 實作熱鍵(HotKey)共用程式,以Konami(上上下下左右左右BA)為例

/*
不曉得有沒有人想過像 Konami 那種,按 "上上下下左右左右BA" 後,
會觸發某些事件的功能,如何撰寫,以下是我的實作,除了特定的 Konami 按鍵外,
也允許自訂自己的按鍵事件序列的測試方法,
整個程式的概念,是註冊 document 的 keyup 事件,並紀錄下最近 10 次的按鍵內容,
並提供一個 RegisterTest 方法,由註冊者自行決定目前按下的按鍵是否符合觸發的條件,
若符合要觸發什麼事件; RegisterTest 會先把這兩個 function 紀錄下來,
等到畫面上的 kepup 事件產生時,逐一呼叫 testFun , testFun 回傳 true ,
就呼叫 triggerFun 執行當初註冊的觸發事件。
這個程式需要 jQuery ,
另外這個陽春的版本不提供組合鍵的處理,當然稍微修改一下也可以支援組合鍵。
*/
var HotKeyControler = {
    MAX_QUEUE: 10, /* 保留最後 10 個鍵盤事件 */
    testFuns: new Object(),
    triggerFuns: new Object(),
    KeyQueue: new Array,
    Keyup: function(event) {
        HotKeyControler.KeyQueue.push(event.keyCode);
        if (HotKeyControler.KeyQueue.length > HotKeyControler.MAX_QUEUE) {
            HotKeyControler.KeyQueue.shift();
        }
        for (var key in HotKeyControler.testFuns) {
            if (HotKeyControler.testFuns[key](event,  HotKeyControler.KeyQueue)) {
                HotKeyControler.triggerFuns[key]();
            }
        }
    },
   /*  函數名稱:註冊熱鍵
    *  參數說明:(唯一鍵值, 測試function, 觸發function)
    *  說   明:提供註冊熱鍵的方法,提供目前畫面被按下的按鍵佇列,供AP自行判斷是否觸發執行事件
    */
    RegisterTest: function(key, testFun, triggerFun) {
        if (typeof(testFun)=='function' && typeof(triggerFun)=='function') {
            HotKeyControler.testFuns[key] = testFun;
            HotKeyControler.triggerFuns[key] = triggerFun;
        }
    }
}

$(document).ready(function(){    
    $(document).unbind("keyup",HotKeyControler.Keyup);
    $(document).bind("keyup",HotKeyControler.Keyup);
});

/* DEMO 註冊當按下 Konami(上上下下左右左右BA)時要觸發的事件 */
HotKeyControler.RegisterTest("Konami"
    ,function(event,code) { /* 上上下下左右左右BA */
        if (code.length!=10) return false;
        if (code[0]==38 && code[1]==38 && code[2]==40 && code[3]==40 &&
            code[4]==37 && code[5]==39 && code[6]==37 && code[7]==39 &&
            code[8]==66 && code[9]==65) {
            return true;
        }
        return false;
    }
    ,function() {
        alert('上上下下左右左右BA!!!');
    }
);

2012年8月20日

[RegExp] 正規表示式(Regular Expression) 應用(一)

網路上有 一大堆 關於 Regular Expression (正規表示式),寫得很棒的文章,
所以這裡並不特別介紹,因為怎麼寫也沒有比別人寫得好,
因此我主要的目的是應用的分享,所謂師傅領進門,修行靠個人,
這篇就是把小弟個人的修行成果做個紀錄,
以後就不定期來更新,

HTML DOM Style 物件擁有很多屬性,
我們可以直接用 element.style[styleProperty] 去存取他們,
偏偏有些舊 IE 時代的程式會這麼寫:
element.style.getAttribute(styleProperty);
element.style.setAttribute(styleProperty, value);
糟糕的是,其它的 browser 不甩這種寫法,而是要這麼寫
element.style.getPropertyValue(style-property)
element.style.setProperty(style-property, value)
更慘的是,新舊參雜的風格時,有大量的程式需要調整、測試,在不動舊程式的情況下,
我們希望在其他瀏覽器下模擬出 style.get/setAttribute 的行為,
於是我這麼做:
if (typeof (CSSStyleDeclaration.prototype.getAttribute) == "undefined") {
    CSSStyleDeclaration.prototype.getAttribute = function (propName) {
        return this.getPropertyValue(Me._changePropName(propName));
    }
}
if (typeof (CSSStyleDeclaration.prototype.setAttribute) == "undefined") {
    CSSStyleDeclaration.prototype.setAttribute = function (propName, value) {
        this.setProperty(Me._changePropName(propName), value);
    }
}
但這卻遇到一個問題,當用 get/set Property 時, propertyName 格式是和 CSS 一樣,
中間用 "-" 分隔,但 IE get/set Attribute 的寫法則是第二個字的字首字母大寫(ex: backgroundColor Vs. background-color),所以我們需要處理一下 propName,
處理的方式則用運用 RE 來處理:
    function changePropName(propName) {
        var pattern = /[A-Z]/;
        var c = ("-" + (propName.match(pattern))).toLowerCase();
        var s = propName.split(pattern);
        var result = s[0];
        if (s.length > 1) {
            result += c + s[1];
        }
        return result;
    }
利用 RE 找出字串中的大寫字元,以 backgroundColor為例
mach 會找出 C
split 則會找出 background,olor
然後就把 'background' + '-' + 'c' + 'olor' 就完成了我們要的轉換了。




2012年7月26日

[.Net] Gotcha: Don't use <xhtmlConformance mode="Legacy"/> if you want to cross browser

好吧,我承認,這標題是學 Gotcha: Don't use <xhtmlConformance mode="Legacy"/> with ASP.NET AJAX 的。

不過情況有些不一樣,最近接手一個舊的 ASP.NET 網站,
雖然是 .Net Framework 2.0 的網站,但骨子裡其實是從 .Net Framework 1.1 升級的,
我們的需求不多,只要可以和時下流行的 Browser 相容就好,
也不打算引入 ASP.NET AJAX,
於是著手開始調整相容性相關的程式寫法,
調整得差不多後,開始測試,
沒想到一開始就遇到 Validator 不能正常執行的情況,
追了一下發現一個很特別的現象,

造成 Validator 不正常的問題出在 Asp.Net 的 ValidatorOnLoad 中,
會直接用 DOMObject.Attribute 去取值(如下程式碼片段),
但這在其他 IE 以外的瀏覽器是不允許的(IE9標準模式也不允許)!
function ValidatorOnLoad() {
if (typeof(Page_Validators) == "undefined")
return;
var i, val;
for (i = 0; i < Page_Validators.length; i++) {
val = Page_Validators[i];
if (typeof(val.evaluationfunction) == "string") {
eval("val.evaluationfunction = " + val.evaluationfunction + ";");
} // 略 .....
正確的作法應該要用 getAttribute('name') 才對,
但這不能解釋為什麼相同的程式碼在 .Net Framework 2.0 卻能正常的運作,
我用 evaluationfunction 這個關鍵字找了半天,
完全沒有找到相關的說明,
後來拿一個運作正常的網站所產生的 HTML 來和有問題的比較,
發現:

一般正常的應該要長得像這樣:

但他卻長成這樣:

到這裡,情況已經很明朗了,應該有什麼特別的設定,造成這樣的差異,
於是我改用別的關鍵字搜尋
.Net 1.0 and 2.0 hybrid validator not work
終於勉強找到了
Why might ASP.NET be putting JavaScript in HTML Comment blocks, not CDATA?
裡面提到了關鍵字 <xhtmlConformance mode="Transitional" />
再用 xhtmlConformance 就找到了 Gotcha: Don't use <xhtmlConformance mode="Legacy"/> with ASP.NET AJAX
原來,如果 xhtmlConformance mode="Legacy" 時,
不會處理那些不是標準 XHTML 的屬性,
反之,則會多產生一段 Javascript 把擴充屬性"塞"給 DOM Object ,
確保在不同的瀏覽器下都能夠用 DOMObject.attribute 的方式取到資料。

參考資料:
1. Why might ASP.NET be putting JavaScript in HTML Comment blocks, not CDATA?
2. Gotcha: Don't use <xhtmlConformance mode="Legacy"/> with ASP.NET AJAX
3. xhtmlConformance 項目 (ASP.NET 設定結構描述)
4. KB-由ASP.NET 1.1昇級的網站無法啟用MS AJAX


2012年7月25日

[Software] 分享一個好用的 link file 建立工具

以往傳統 Windows 的使用者,通常只知道所謂的捷徑(而且被捷徑制約很深),
Windows 的捷徑,只是一個文件,描述實際檔案路徑的位置,
透過程式(如Explorer)讀取捷徑文件的內容,再轉譯成實際檔案路徑,
因此,如果程式不是透過 Windows 的機制開啟捷徑所指的檔案,
那開起來的就會是捷徑本身。

Link file 這個概念,有點類似別名,
就好像我們知道 "第三者"、"小三" 指的都是同一件事情,
我們也可以把他想成是捷徑的進階版,而這個進階版,
則是需要作業系統及檔案系統都支持才能達成的,

所謂的檔案系統,就是我們資料儲存在儲存裝置上的方式,
常有人以為,儲存裝置(如硬碟)就像抽屜一樣,把文件通通都疊在裡面,
其實不然,當我們儲存一份文件到硬碟的時候,
作業系統會在一個類似書的目錄頁中,
建立一個索引,指向實際檔案內容的位置,
當我們在列出檔案清單的時候,並不是像在抽屜中一疊一疊的翻找,
而是將建立在目錄頁中的內容列出來,
當我們要開啟某份文件的時候,作業系統才透過該索引取得實際的檔案內容,

符號連結的概念,就是製作一個符號,指向檔案系統建立的目錄頁中的檔案,
而這個符號看起來就像真的指向實際檔案內容一樣,
所以就算一般程式在開啟這個符號連結的時候,
也不用考慮他是不是捷徑,需不需要額外處理,因為他們指的是同一件事情,
概念就介紹到這裡,至於 Symbolic link 、 Hard link 、 Junction 有什麼不同,
有興趣的話可以到以下網站參考
http://en.wikipedia.org/wiki/Hard_link
http://linux.vbird.org/linux_basic/0230filesystem/0230filesystem.php
http://en.wikipedia.org/wiki/NTFS_symbolic_link
http://webcache.googleusercontent.com/search?q=cache:2FHejrp9CZAJ:blog.miniasp.com/post/2011/09/17/How-to-create-NTFS-Reparse-Points-Symbolic-and-Hard-Links.aspx+&cd=2&hl=zh-TW&ct=clnk&lr=lang_zh-TW
http://jdev.tw/blog/729/vista-soft-and-hard-link

現在 Windows 7 之後的版本,提供了 mklink 的指令,
方便我們建立符號連結,但還是不夠直覺,
於是我找到了 Link Shell Extension
他的操作方式很簡單,

首先在 "來源的檔案或目錄" 上按右鍵(如下圖),選擇 Pick Link Source。



接下來在 "目的位置" 按右鍵(如下圖),選擇 Drop As... --> Hardlink(同一磁碟機才會出現) 或 Symbolic Link。

完成後結果如下圖:
選擇 Symbolic link 時,會出現綠色的捷徑符號,佔用的檔案大小為 0 ,
現在這個檔案就代表他的來源檔案,對一般應用程式而言,他們是沒有差別的!

選擇了 Hardlink 的話,來源及目的的檔案都會出現紅色的捷徑符號,
檔案大小也會一樣,但實際只佔一份檔案的硬碟空間,
Hardlink 和捷徑的概念不同,刪掉來源檔案,並不能真正將檔案從磁碟機中刪除,
要把所有 Hardlink 到這份檔案的連結都刪光才會真正的把空間釋放出來。


Link Shell Extension 讓我們就像 Windows 操作檔案的習慣一樣,操作 link file,是一個相當實用的工具,想瞭解更多也可以到 Link Shell Extension 網站上看個明白。

2012年4月11日

[Mac] WinShortcutter 讓 Mac 悠遊 Windows 的網路芳鄰

WinShortcutter 這玩意兒太讚了,竟然沒有中文的介紹,實在可惜。
首先先前情提要一下,
自從我把 Notebook 換成 MBA 後,日常作業中,最讓我困擾的問題就是,
沒有辦法很自在的快速切換到別台 Windows 的共用資料夾,
雖然可以
在 finder 下按 Command + k ,然後輸入 smb://yourWinServer/sharedFolderName
但如果是在文件中看到的是長這樣的路徑
\\yourWinServer/sharedFolderName
那就要很辛苦的把 "\" 換成 "/" 如果很多層的話那心情就會很糟。
所以我一直在尋找解決方案,
終於皇天不負苦心人,讓我找到了 WinShortcutter 這個法寶,
安裝過程一般都沒什麼問題,安裝完成後,它會請你重新開機,
這時候別不信邪,因為服務要更新,所以請重新開機,
開完機後(其實安裝完後就可以看到這個改變了),
回到 finder 中就可以看到,原本 Mac 不認識的 *.lnk 的圖示變了,


雙擊後就會有神奇的效果出現 ... 是的 ... Mac 就學會了如何 follow the link。
當然,神奇的還不止這樣,
接下來要請你跟我這樣做(下面的動作請在重新開完機後做,不然會找不到服務),

首先,打開你的 Finder -> 服務 ->  服務偏好設定(如下圖)

在服務偏號設定 ->服務 裡面尋找
Copy Path to Clipboard 及 Open as Windows Link 並把他勾起來


接著就可以用 選取文字 -> 右鍵 的方式,在選單 -> 服務 -> Open as Windows Link

在 Finder 裡,則是可以 選取檔案或資料夾 -> 右鍵 -> 服務 -> Copy Path to Clipboard

複製出來的檔案路徑如果要把 "/" 變成 "\"  可以在

系統偏好設定 -> WinShortcutter 中,把 Use Backward Slashes 勾起來即可

*.lnk 也可以用快速查看,查看捷徑內容



是不是很方便呢!


WinShortcutter 網址:這裡