2013年10月21日 星期一

Cool Buttons 超連結變漂亮的按鈕

Wow, 好厲害,透過 <A> 超連結 tag 就做出各式各樣漂亮的 button,真的是太神奇了,立馬把 css 摳回家。

這裡有扁平設計(Flat Design)、漸變陰影 (Gradient)、3D 按鈕等一應俱全喔 (^_^)


URL: http://fribly.com/2013/10/18/cool-buttons/

2013年7月24日 星期三

Android 多國語系 strings.xml 檔案轉 iOS InfoPlist.strings

同時間開發 Android 和 iOS 時, 要共用其設定好的多國語言設定檔的話, 可以透過 Notepad++ 的 RegExp 來幫忙轉換.

Android to iOS:
Find: <string name="
([A-Z0-9_\-.%@ ':]+)">(.*)</string> 
Replace as: "\1"="\2";

iOS to Android:
Find: "([A-Z0-9_\-.%@ ':]+)"="(.*)";
Replace as: <string name="\1">\2</string>

Notepad++官方說明文章如下:

Notepad++ RegExp List
Note: In case you have the plugins installed, try CONTROL+R or in the Menu Plugins � TextFX Quick - Find/Replace to get a sophisticated dialogue including a drop down for regular expressions and multi line search/replace.
In a regular expression, special characters interpreted are:
.Matches any character
(This marks the start of a region for tagging a match; so what's inside ( ) you can use in "replace with" using \1, \2 etc.
)This marks the end of a tagged region.
\nWhere n is 1 through 9 refers to the first through ninth tagged region when replacing. For example, if the search string was Fred([1-9])XXX and the replace string was Sam\1YYY , when applied to Fred2XXX this would generate Sam2YYY .
\<This matches the start of a word using Scintilla's definitions of words.
\>This matches the end of a word using Scintilla's definition of words.
\xThis allows you to use a character x that would otherwise have a special meaning. For example, \[ would be interpreted as [ and not as the start of a character set.
[...]This indicates a set of characters, for example, [abc] means any of the characters a, b or c. You can also use ranges, for example [a-z] for any lower case character.
[^...]The complement of the characters in the set. For example, [^A-Za-z] means any character except an alphabetic character.
^This matches the start of a line (unless used inside a set, see above).
$This matches the end of a line.
*This matches 0 or more times. For example, Sa*m matches Sm , Sam , Saam , Saaam and so on.
+This matches 1 or more times. For example, Sa+m matches Sam , Saam , Saaam and so on.

Source of this information is the Scintilla edit component help, but it was adapted to Notepad++ behaviour.
Notepad++ RegExp Examples
Important
  • You have to check the box "regular expression" in search & replace dialog
  • When copying the strings out of here, pay close attention not to have additional spaces in front of them! Then the RegExp will not work!
You use a MediaWiki (e.g. Wikipedia, Wikitravel) and want to make all headings one "level higher", so a H2 becomes a H1 etc.
  1. Search ^=(=)
    Replace with \1
    Click "Replace all" to find all headings2...9 (two equal sign characters are required) which begin at line beginning (^) and to replace the two equal sign characters by only the last of the two, so eleminating one and having one remaining.
  2. Search =(=)$
    Replace with \1
    Click "Replace all" to find all headings2...9 (two equal sign characters are required) which end at line ending ($) and to replace the two equal sign characters by only the last of the two, so eleminating one and having one remaining.
  3. == title == became = title =, you're done :-)
You have a document with a lot of dates, which are in German date format (dd.mm.yy) and you'd like to transform them to sortable format (yy-mm-dd). Don't be afraid by the length of the search term – it's long, but consiting of pretty easy and short parts.
  1. Search ([^0-9])([0123][0-9])\.([01][0-9])\.([0-9][0-9])([^0-9])
    Replace with \1\4-\3-\2\5
    Click "Replace all" to fetch
    • the day, whose first number can only be 0, 1, 2 or 3
    • the month, whose first number can only be 0 or 1
    • but only if the spearator is . and not any charcter ( . versus \. )
    • but only if no numbers are sourrounding the date, as then it might be an IP address instead of a date
    and to write all of this in the opposite order, except for the surroundings. Pay attention: Whatever SEARCH matches will be deleted and only replaced by the stuff in the REPLACE field, thus it is mandtory to have the surroundings in the REPLACE field as well!
  2. 31.12.97 became 97-12-31 and 14.08.05 became 05-08-14 and the IP address 14.13.14.14 did not change, you're done :-)
You have printed in windows a file list using dir /b/s >filelist.txt to the file filelist.txt and want to make local URLs out of them.
  1. Open filelist.txt with Notepad++
  2. Search \\
    Replace with /
    Click "Replace all" to change windows path separator char \  into URL path separator char / 
  3. Search ^(.*)$
    Replace with file:///\1
    Click "Replace all" to add file:/// in the beginning of all lines
  4. Depended on your requirements, preceed to escape some characters like space to %20 etc.
  5. C:\!\aktuell.csv became file:///C:/!/aktuell.csv, you're done :-)
Another Search Replace Example
[Data]
EU AX ALA 248 �land Islands
EU AL ALB 008 Albania, People's Socialist Republic of
AF DZ DZA 012 Algeria, People's Democratic Republic of
OC AS ASM 016 American Samoa
EU AD AND 020 Andorra, Principality of
AF AO AGO 024 Angola, Republic of
NA AI AIA 660 Anguilla
AN AQ ATA 010 Antarctica (the territory South of 60 deg S)
NA AG ATG 028 Antigua and Barbuda
SA AR ARG 032 Argentina, Argentine Republic
AS AM ARM 051 Armenia
NA AW ABW 533 Aruba
OC AU AUS 036 Australia, Commonwealth of
[SearchPattern]
([A-Z]+) ([A-Z]+) ([A-Z]+) ([0-9]+) (.*)
[ReplacePattern]
\1,\2,\3,\4,\5
[FinalData]
AS,AF,AFG,004,Afghanistan
EU,AX,ALA,248,�land Islands
EU,AL,ALB,008,Albania, People's Socialist Republic of
AF,DZ,DZA,012,Algeria, People's Democratic Republic of
OC,AS,ASM,016,American Samoa
EU,AD,AND,020,Andorra, Principality of
AF,AO,AGO,024,Angola, Republic of
NA,AI,AIA,660,Anguilla
AN,AQ,ATA,010,Antarctica (the territory South of 60 deg S)
NA,AG,ATG,028,Antigua and Barbuda
SA,AR,ARG,032,Argentina, Argentine Republic
AS,AM,ARM,051,Armenia
NA,AW,ABW,533,Aruba
OC,AU,AUS,036,Australia, Commonwealth of

2013年5月22日 星期三

[C] Sudoku

上次寫C語言, 可能是在2專的時候, 在 1997年或 1998年, 這次挑戰用C 語言寫數獨(Sudoku) 的程式, 大約 0.009秒就可以計算出簡單版本數獨的結果. 試了一下, 好像要用亂數來幫忙做決定猜測的流程.

input:
194800203500012096203495008935700682418029500726500900600058324840936015000070060

output:
1948XX2X35XXX12096...
(Where X means any one of 1..9)


以下的程式只能解決簡單的情況, 無法解決 "需要去猜測" 的情況.

#include <stdio.h>

int option_ary[81][10];

//int main(int argc, char *argv[])
int main()
{
    /*
    if(argc < 2 || argc > 3)
    {
        printf("Pleae input one paramater.\n");
        return 0;
    }
    */
 
    //printf("input string is %s.\n",argv[1]);
    //printf("input strlen is %d.\n",strlen(argv[1]));
   
    char input_string[256];
    scanf("%s",input_string);
    //printf("your string: %s",input_string);
    //return 1;
   
    int i,j;
   
    // initial options table.
    // first [i][0] is the final answer.
    for(i=0;i<81;i++){
        option_ary[i][0]=0;
        for(j=1;j<=9;j++){
            option_ary[i][j]=j;
        }
    }
   
    // get input data.
    for(i=0;i<81;i++){
        //int input_value = argv[1][i]-48;
        int input_value = input_string[i]-48;
        option_ary[i][0]=input_value;
        if(option_ary[i][0]>0){
            remove_optins_in_table(i,input_value);
        }
    }

    int check_count=0;
    int check_result=0;
    for(i=0;i<100;i++){
        check_count++;
        check_result=check_answer(0);
        if(check_result==1)
        {
            break;
        }
    }
   
    // 無解(或多種解), 開始猜測可能的解.
    // (沒寫)

    if(check_result==1){
        printf("finish, check count:%d",check_count);
    }
    else
    {
        printf("check fail..., no answer.");
    }


   
    // test convert index
    //int index=80;
    //printf("row/col/block: %d/%d/%d", get_row(index), get_col(index), get_block(index));

    // output final answer
    //printf("\n");
    for(i=0;i<81;i++){
        printf("%d",option_ary[i][0]);
        /*
        if(i % 9==8){
            printf("\n");
        }
        */
    }
    return 1;
}


// purpose: check answer
// return:
//      1: finish.
//      0: not finish.
int check_answer(int try_to_guess)
{
    int ans_i=0,ans_j=0;
    int unknow_value_count=0;
    for(ans_i=0; ans_i<81; ans_i++)
    {
        int zero_count=0;
        int last_value=0;
        if(option_ary[ans_i][0]==0){
            unknow_value_count++;
            // value un-know.
            for(ans_j=1; ans_j <=9; ans_j++)
            {
                if(option_ary[ans_i][ans_j]==0)
                {
                    zero_count++;
                }
                else
                {
                    if(last_value==0){
                        // looking for first value.
                        last_value=option_ary[ans_i][ans_j];
                    }
                    // looking for last value.
                    //last_value=option_ary[ans_i][ans_j];
                }
            }
            if(zero_count==8)
            {
                // bingo.
                //printf("\n bingo %d,%d\n",ans_i,last_value);
                option_ary[ans_i][0]=last_value;
                remove_optins_in_table(ans_i,last_value);
            }
            else {
                if(zero_count==(8-try_to_guess)){
                    // two element, guess last one.
                    if(try_to_guess>=1){
                        option_ary[ans_i][0]=last_value;
                        remove_optins_in_table(ans_i,last_value);
                    }
                }
            }
        }
    }
   
    int return_value=0;
    if(unknow_value_count==0)
    {
        return_value=1;
    }
   
    return return_value;
}

int get_row(int index)
{
    return (index/9)+1;
}

int get_col(int index)
{
    return index%9;
}

int get_block(int index)
{
    int base_row=(index/27)*3+(index%9/3)+1;
}

int remove_optins_in_table(int index, int input_value)
{
    // remove options in row.
    remove_options_in_row(index, input_value);
   
    // remove options in col.
    remove_options_in_col(index, input_value);
   
    // remove options in block.
    remove_options_in_block(index, input_value);
}

// remove options in row.
int remove_options_in_row(int index, int input_value)
{
    int i=0;
    for(i=0; i<9; i++)
    {
        int target_index=((index/9)*9)+i;
        option_ary[target_index][input_value]=0;
    }
   
    return 1;
}

// remove options in col.
int remove_options_in_col(int index, int input_value)
{
    int i=0;
    for(i=0; i<9; i++)
    {
        int target_index=(index%9)+(i*9);
        option_ary[target_index][input_value]=0;
    }
   
    return 1;
}

// remove options in block.
int remove_options_in_block(int index, int input_value)
{
    int i=0;
    int target_block=get_block(index);
    for(i=0; i<81; i++)
    {
        if(get_block(i)==target_block)
        {
            option_ary[i][input_value]=0;
        }
    }
   
    return 1;
}

2012年10月18日 星期四

[XSL] 在 xsl 裡做字串取代


要在 xsl 裡做字串取代, 有2個方法, 參考看看下面的範例:
1. translate
translate(string,':',', ')


2. template
<xsl:template name="replace-string">
    <xsl:param name="text"/>
    <xsl:param name="replace"/>
    <xsl:param name="with"/>
    <xsl:choose>
      <xsl:when test="contains($text,$replace)">
        <xsl:value-of select="substring-before($text,$replace)"/>
        <xsl:value-of select="$with"/>
        <xsl:call-template name="replace-string">
          <xsl:with-param name="text" select="substring-after($text,$replace)"/>
          <xsl:with-param name="replace" select="$replace"/>
          <xsl:with-param name="with" select="$with"/>
        </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$text"/>
      </xsl:otherwise>
    </xsl:choose>
</xsl:template>


call template example:
<xsl:call-template name="replace-string">
  <xsl:with-param name="text" select="'aa::bb::cc'"/>
  <xsl:with-param name="replace" select="'::'" />
  <xsl:with-param name="with" select="','"/>
</xsl:call-template>




資料來源:
xslt 1.0 string replace function
http://stackoverflow.com/questions/7520762/xslt-1-0-string-replace-function

Replace
http://www.dpawson.co.uk/xsl/sect2/replace.html#d8766e61

2012年10月17日 星期三

[Asp].ASPStringBuilder 和 .net stringer builder 效能比較

最近遇到一個功能, 要匯出資料, 因為效能很差, 要匯很久, 想看看用純 asp 處理比較快, 還是改用 .net 比較快, 分別測試2種方法, 結論是差不多, 要快的話, 中間使用的流程/架構要翻掉, 改用其他方法, 才會有效率.

這一個個案遇到的是 XML 的內容就 71mb, 含 DOM object 一起的話RAM 大約占用 150mb, 在 apply style 套 xsl 時, 瞬間的 RAM 再會多用掉500多MB, 像寫的這麼爛的程式, 真的很怕有很多人同時點下去執行, 應該要把Web Server 弄掉 memory overflow 錯誤.


有圖有真相, 畫面如下:

測試方案1號: 使用 ASP 寫的 StringBuilder(cASPString.asp),
圖片1.1: 程式執行前的RAM, 大約用掉 127mb:

圖片1.2: 程式執行前的RAM, DOM 大約用掉 150mb 左右:
(說明: 由於沒有在用 RAM 回收的太快, 所以數字不太準.)


測試方案2號: 使用 ASP 呼叫 .Net StringBuilder
圖片2.1: 程式執行前的RAM, 大約用掉 133mb:

圖片2.2: 程式執行前的RAM, DOM 大約用掉 150mb 左右:


在 DOM ready 好的時候, 可以看到 RAM 的使用快速成長, 會從 300mb 變 500, 再長大到 700mb, 最後在 860mb 右右停留 2~3秒, 再回來 200MB 左右.

測試結論: 在 Asp 使用 StringBuilder(cASPString.asp), 和呼叫 .Net 的 String Builder 效能上差不多, 主要的瓶頸在 XML 太大.


附上, asp 版本的 StringBuilder.
'====================================
' MODULE:    cASPString.asp
' AUTHOR:    www.u229.no
' CREATED:   May 2006
'====================================
' COMMENT: A fast string class for classic ASP.               
'====================================
' ROUTINES:

' - Public Property Get NumberOfStrings()
' - Public Property Get NumberOfBytes()
' - Public Property Get NumberOfCharacters()
' - Private Sub Class_Initialize()
' - Public Sub Append(sNewString)
' - Public Function ToString()
' - Public Sub Reset()
'====================================


'====================================
Class ASPStringBuilder
'====================================

'// MODULE VARIABLES
Dim m_sArr            '// Array holding the strings.
Dim m_lResize        '// Factor for rezising the array.
Dim m_lStrings       '// Number of strings appended.

'// PROPERTIES
Public Property Get NumberOfStrings()
    NumberOfStrings = m_lStrings
End Property

Public Property Get NumberOfBytes()
    NumberOfBytes = LenB(Join(m_sArr, ""))
End Property

Public Property Get NumberOfCharacters()
    NumberOfCharacters = Len(Join(m_sArr, ""))
End Property

'------------------------------------------------------
' Comment: Initialize default values.
'------------------------------------------------------
Private Sub Class_Initialize()
    m_lResize = CLng(50)
    m_lStrings = CLng(0)
    ReDim m_sArr(m_lResize)
End Sub

'------------------------------------------------------
' Comment: Add a new string to the string array.
'------------------------------------------------------
Public Sub Append(sNewString)

    If Len(sNewString & "") = 0 Then Exit Sub
    
    '// If we have filled the array, resize it.
    If m_lStrings > UBound(m_sArr) Then ReDim Preserve m_sArr(UBound(m_sArr) + m_lResize)

    '// Append the new string to the next unused position in the array.
    m_sArr(m_lStrings) = sNewString
    m_lStrings = (m_lStrings + 1)
End Sub

'------------------------------------------------------
' Comment: Return the strings as one big string.
'------------------------------------------------------
Public Function ToString()
    ToString = Join(m_sArr, "")
End Function

'------------------------------------------------------
' Comment: Reset everything.
'------------------------------------------------------
Public Sub Reset()
    Class_Initialize
End Sub

'====================================
End Class 
'====================================

讓 asp call .net string builder 的方法:
namespace MyObject
{
    public class StringBuilderObject
    {
        public StringBuilder MyStringBuilder = new StringBuilder("");
        public StringBuilderObject()
        {
        }
        public void Append(string value)
        {
            this.MyStringBuilder.Append(value);
        }
        public void Remove(int startIndex, int length)
        {
            this.MyStringBuilder.Remove(startIndex, length);
        }
        public override string ToString()
        {
            return this.MyStringBuilder.ToString();
        }
    }
}

2012年10月16日 星期二

[Asp].實作網頁匯出為 PDF 的心得

今天分享我在實作網頁匯出為 PDF 的心得, 今天遇到2個問題:
Q 1. 在後台匯出的網頁, 要匯出的網頁必需為 "登錄狀態" 才能匯出, 可是使用 wkhtmltopdf 時, 無法取得登入狀態.
Q 2. wkhtmltopdf 元件匯出的資料夾, 無法和中文目錄相容, 會有Error 產生, 訊息如下:
匯出檔案到 E:\oooo網\ 這個 "網" 是中文字, 造成Error.


Q 1. 首先解決, 登錄狀態, 的問題,
A 1: 這個解決方法很多很多,

方案1.1: 直接輸入 userid.
這是最快的解決方法, 直接寫一支 app_sso.asp 程式, 傳入 userid 就解決了. 可是這個解法變成, 萬一我是離職的工程師, 我事先就知道這支程式, 在user 傳入 userid 就可以取得該帳號的權限, 這樣問題就比較大.


方案1.2: 修改後台的程式, 遇到某些參數傳進來時(例如: ?exportPdf=true), 就開放部份的權限, 允許顯示資料庫內容. 這個解法也不錯.


方案1.3: session userid 放database, 透過 tokenid 來存取.
和上面的方案1.1相比, 這個方案1.3 會安全一點點, app_sso.asp 處理步驟如下:

1.3.1. 後台把 session userid 放進 table,
1.3.2. 取出 table 裡的 token_id,
1.3.3. 把 token_id 給下一支沒有 session 的 app,
1.3.4. 沒有 session 的 app request token_id 後, 透過 tokenid 再到 database 裡去取出 userid.
1.3.5 模擬使用者登入, 並重導(Redirect) 到實際上要產生為 PDF 的 URL.


附上使用到的 table schema:
CREATE TABLE [dbo].[webkitHtmlToken](
 [id] [int] IDENTITY(1,1) NOT NULL,
 [tokenID] [uniqueidentifier] NOT NULL CONSTRAINT [DF_webkitHtmlToken_tokenId]  DEFAULT (newid()),
 [userid] [nvarchar](20) NOT NULL,
 [siteid] [varchar](50) NULL,
 [status] [char](1) NULL CONSTRAINT [DF_webkitHtmlToken_status]  DEFAULT ('I'),
 [sessionid] [int] NULL,
 [createdDate] [smalldatetime] NULL CONSTRAINT [DF_webkitHtmlToken_createdDate]  DEFAULT (getdate()),
 CONSTRAINT [PK_webkitHtmlToken] PRIMARY KEY CLUSTERED
(
 [tokenID] ASC
)
)
說明: 可能專案不同, 有可能需要多增加, 或刪除相關欄位.


Q 2: 中文字,
A 2:這個要解就很簡單, 先透過副程式判斷是否有中文字, 有的話就使用暫存的資料夾.


最後, 整個流程方式, 我的做法是:
2.1 在AP設定檔裡增加2個設定值,
(a)wkhtmltopdf 執行檔路徑
(b)wkhtmltopdf 遇到中文字路徑時, 要使用的暫存資料夾, 這個記得要允許 IUSER 有寫入及刪除檔案的權限, 例如:
<wkhtmltopdf>
    <path>C:\wkhtmltopdf\wkhtmltopdf.exe</path>
    <tempFolder>C:\wkhtmltopdf\tempFolder</tempFolder>
</wkhtmltopdf>

2.2 寫副程式, 取得 tokenid
'// purpose: 放入 session info 到 database, 取得 tokenid.
'// ex: ret = getWebkitHtmlTokenID(conn, session("userid"), session.sessionid, session("siteid"))
function getWebkitHtmlTokenID(byref conn, byval userid, byval sessionid, byval siteid)
...
...
end function


2.3 寫副程式, 取得 tokenid
'// purpose: 輸出某一個 URL 到實體檔案.
'// call HtmlToPdfFile(pdfUrl, user_output_filepath)
function HtmlToPdfFile(byval pdfUrl, user_output_filepath)
...
...
end function

2.4 寫一支  export_pdf.asp
2.4.1 先呼叫 2.2 的附程式取得 tokenID.
2.4.2 再把 tokenID 放入 app_sso.asp, 並設定實際要匯入資料的 URL.
2.4.3 輸出某一個 URL 到實體檔案, 在這個副程式裡執行外部指令(要等待程式回應, Wait For Single), 等待wkhtmltopdf.exe 匯出pdf 完成程式再繼續往下執行.
2.4.4 透過 ADODB.Stream, 取出檔案內容, 並 Response.BinaryWrite objStream.Read
2.4.5 最後, 完成後, 刪掉暫存檔, 後台匯出pdf 完成.
call DeleteFile(user_output_filepath)


說明: 前台(無登錄狀態)匯出 pdf 的話就會簡單很多, 少掉取得 tokenid 這一個 step.

增加這個功能, 會用到的相關檔案及說明:


  • ApConfig.xml , 參數設定
  • app_sso.asp , 讓外部程式模擬登入
  • export_pdf.asp , 實際上匯出的 app.
  • pdf.Function.asp , 副程式.





相關文章:
[Asp].用正規表示式檢查字串是不包含中文字
http://maxtellyou.blogspot.tw/2012/10/asp.html

[Asp].用正規表示式檢查字串是不包含中文字

今天遇到一個問題, wkhtmltopdf 在輸出 pdf 時, 無法輸出檔案到中文的資料夾下, 會發生錯誤:
Error: Unable to write to destination


我想到的解決辦法是, 先放到一個暫存的資料夾, 再搬到有中文的真正的目的地, 即可, 所以要先判斷輸出的資料夾, 是否包含中文, 附件 Asp 用的檢查副程式.


'// RegExp Test.
Function RegExpTest(byval patrn, byval str)
    Dim regEx
    Set regEx = New RegExp
    regEx.Pattern = patrn
    regEx.IgnoreCase = True
    regEx.Global = True
    RegExpTest = regEx.Test(str)
End Function


'// 檢查文字中是否有中文字
'// ex: ret = IsMatchChinese(str)
'// return:
'//     True: find.
'//     False: not found.
Function IsMatchChinese(byval str)
    IsMatchChinese = RegExpTest("[一-龥]+",str)
End Function

呼叫的範例如下:
if IsMatchChinese(output_filepath) then
    response.write "<br/>bingo, match chinese folder..."
else
    response.write "<br/>ok, not chinese folder continue..."
end if


聽說 Asp.Net 的範例如下, 還沒測試過:
Regex ex = new Regex("[一-龥]"); 
bool isMatched = ex.IsMatch("jjsss 中文 ksks");

Facebook 留言板