2024年2月29日 星期四

AngularJS - 自製分頁 Filter

紀錄一下 AngularJS 自製分頁 Filter 的方法

HTML:

<div ng-app="app" ng-controller="controller as ctrl">
  <div ng-repeat="item in ctrl.itmeList | paging:ctrl.page:ctrl.pageSize">
    {{item}}
  </div>
  
  <div>  
    Page: <input type="number" ng-model="ctrl.page"/>
  </div>
  <div>  
    Page size: <input type="number" ng-model="ctrl.pageSize"/> Page
  </div>
</div>

Javascript:

var app = angular.module("app", []);
app.controller("controller", [function() {
	var self = this;
  self.itmeList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
  self.page = 1;
  self.pageSize = 3;
}]);

app.filter('paging', function() {
	    return function(input, page, pageSize) {
	        return input.slice(page * pageSize - pageSize, page * pageSize);
	    }
});

JsFiddle 範例:

參考資料:

  1. Filters

Javascript 濾掉 Object 中特定屬性的方法

紀錄一下 Javascript 濾掉 Object 中特定屬性的方法,
可以利用 reduce() 來達成,實作程式碼範例如下:

var obj = {"1": 1, "2": 2, "3": 3};
obj = Object.keys(obj).filter(function(key) {
	return key == 1 || key == 3; //濾出值為 1 和 3 的 key
}).reduce(function(newObj, key){
  newObj[key] = obj[key];
	return newObj;
}, {});
console.log(obj); // {1: 1, 3: 3}

Code Point, Surrogate Pair 和 HTML Unicode 編碼

在這篇文章中,我要來紀錄一下 Code Point (字符代碼、碼位、編碼位置) 、Surrogate Pair (代理對)、HTML Unicode 編碼之間的關係,並且示範一下在 Java 中儲存的 UTF-16 字元要如何轉成可在 HTML 中正確顯示的 Unicode 編碼。

首先,先說明一下以下名詞的意義:

  1. Code Point (字符代碼、碼位、編碼位置):
    Code Point 是一個字符的 Unicode 編碼代碼。
    參考 Unicode字元平面對映,Unicode 目前分成 17 組 平面 (Plane),每平面有 65536 (2 的 16 次方),編碼範圍從 0x0000 到 0x10FFFF ,每一個碼位代表某一個字符 (目前還沒有有全部使用到),例如
    "我" 的 Code Point 是 25105 (也就是 0x6211)
    "👍" 的 Code Point 是 128077 (也就是 0x1F44D)
  2. Surrogate Pair (代理對):
    Surrogate Pair 是一種儲存表示字符的一種方式,專門用來儲存 UTF-16 無法用 16 Bit 表示的字符,
    以 "👍" 為例,
    對於 Code Point 是 0xFFFF 以下的字符 UTF-16 會用 16 Bit (2 Byte) 去儲存,
    而對於 "👍" 這種 Code Point 大於 0xFFFF 的字符則會使用 Surrogate Pair 的方式用 32 Bit (4 Byte) 來儲存。

    規則如下,括弧中是以 "👍" 做的例子:
    先將字符的 Code Point 減去 0x10000 (0x1F44D - 0x10000= 0xF44D),
    把得到的值高位部份用 0 填充得到一個 20 Bit 的結果 (0000 1111 0100 0100 1101),
    再將前 10 Bit 高位加上 110110 成為 High Surrogate (1101 1000 0011 1101 = 0xD83D = 55357),
    將後 10 Bit 高位加上 110111 成為 Low Surrogate (1101 1100 0100 1101 = 0xDC4D = 56397),
    最後 UTF-16 會用 High Surrogate + Low Surrogate 為一組的方式 (稱為 Surrogate Pair) 用 32 Bit (4 Byte) 來表示這樣一個字符 ,以  "👍"  來說就是 0xD83D 0xDC4D。

    可以注意到,因為 1101 10 和 1101 11 已作為辨識 High/Low Surrogate 的用途,
    所以 1101 1000 0000 0000 (0xD800) ~ 1101 1111 1111 1111 (0xDFFF) 之間的值不會對應到 任何一個 UTF-16 字符,
    而在 Unicode 中,0xD800 ~ 0xDCFF 之中的 Code Point 也是設計給 UTF-16 的 Surrogate 使用的區塊,
    也就沒有字符的 Code Point 在 0xD800 ~ 0xDCFF 之中。
    可以參考 Unicode字元平面對映

    以 Java 來說,字符是用 UTF-16 來儲存的,
    所以 "👍" 跟 "\ud83d\udc4d" 是等價的。

  3. HTML Unicode 編碼 (或稱 HTML Character Entity 、HTML 字符實體編碼):
    在 HTML 中可以使用 Unicode 表示特殊字符,可以直接使用 10 進位及 16 進位以 Code Point 表示,以 "👍" 為例,可以用 &#128077; 或 &#x1f44d 來表示,
    注意這裡不需使用 Surrogate Pair ,因為 HTML Unicode 是支援大於 0xFFFF 的 Code Point 字符的。

    同樣的,使用 CSS 的時候,也不需要使用 Surrogate Pair,例如:
    <style>
        div:after {
          content: "\1f44d";
        }
      </style>
    

    不過要注意的是,如果是使用 Javascript 的話,還是需要使用 Surrogate Pair 的表達方式,例如一樣以 "👍" 為例:
    console.log("\ud83d\udc4d");
    

最後,用 Java 示範一下上述的觀念,可以配合著看來更好的理解實際上的例子:
使用 JDK 11,
Maven Dependency :

<!-- https://mvnrepository.com/artifact/commons-lang/commons-lang -->
	<dependency>
	    <groupId>commons-lang</groupId>
	    <artifactId>commons-lang</artifactId>
	    <version>2.4</version>
	</dependency>
	
    <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
	<dependency>
	    <groupId>org.apache.commons</groupId>
	    <artifactId>commons-lang3</artifactId>
	    <version>3.14.0</version>
	</dependency>
	
	<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-text -->
	<dependency>
	    <groupId>org.apache.commons</groupId>
	    <artifactId>commons-text</artifactId>
	    <version>1.11.0</version>
	</dependency>

Java:

package test;

import java.util.HashMap;
import java.util.Map;

public class UTF16Test {

	public static void main(String[] args) {
		//👍
		//Code Point (字符代碼、碼位、編碼位置): 0x1f44d = 128077 (10進位)
		//UTF-16 Surrogate Pair: \ud83d \udc4d = 55357 56397 (10進位)
		//High Surrogate: \ud83d
		//Low Surrogate: \udc4d
		//HTML Character Entity (HTML 字符實體編碼): &128077
		String str = "👍";
		
		//確認是 Code Point = 128077 是否就是 "👍"
		System.out.println(Character.toString(128077).equals(str)); // true
		
		//比較 "👍" 是否與 "\ud83d\udc4d" 等價
		System.out.println("\ud83d\udc4d".equals(str)); // true
		
		//"👍" 字串長度為2,因為是用2個 16 Bit 表示,一個 16 Bit 被當成1個 Char
		System.out.println(str.length()); // 2
		System.out.println(str.toCharArray().length); // 2
		
		//"👍" 只含有一個 Code Point,所以字串的 Code Point 陣列長度是 1
		System.out.println(str.codePoints().toArray().length); // 1
		
		//org.apache.commons.lang.StringEscapeUtils.escapeHtml(String) 
		//無法正確地將 "👍" 編碼成 HTML Character Entity,應該算是 Bug ?
		//輸出是 &#55357;&#56397;
		//這種編碼是無法在 HTML page 裡正常顯示的
		System.out.println(org.apache.commons.lang.StringEscapeUtils.escapeHtml(str)); // &#55357;&#56397;
		
		//org.apache.commons.text.StringEscapeUtils 不會對 👍 進行編碼處理,
		//所以輸出不變一樣是 "👍" ,
		//不過不編碼一樣是不能在 HTML page 裡正常顯示的
		System.out.println(org.apache.commons.text.StringEscapeUtils.escapeHtml4(str)); //👍
		
		//示範如何正確地將 "👍" 轉成可以在 HTML page 正常顯示的 HTML Character Entity,
		//也就是 &#128077;
		System.out.println(convertSurrogatePairToHtmlCode(str)); //&#128077;
		
		//示範如何從 Code Point 計算 Surrogate Pair (包括 High Surrogate 和 Low Surrogate)
		//以 "👍" 為例, Surrogate Pair 就是 \ud83d \udc4d = 55357 56397 (10進位)
		Map<String, Integer> surrogatePair = calculateSurrogatePairFromCodePoint(str.codePointAt(0));
		int highSurrogate = surrogatePair.get("highSurrogate");
		int lowSurrogate = surrogatePair.get("lowSurrogate");
		System.out.println(highSurrogate); // 55357
		System.out.println(lowSurrogate); // 56397
		System.out.println(Character.isHighSurrogate((char) highSurrogate));
		System.out.println(Character.isLowSurrogate((char) lowSurrogate));
		
		//示範如何從 Surrogate Pair 計算 Code Point
		//以 "👍" 為例, Code Point 就是 0x1f44d = 128077 (10進位)
		int codePoint = calculateCodePointFromSurrogatePair(highSurrogate, lowSurrogate);
		System.out.println(codePoint); // 128077
		System.out.println(Character.isSupplementaryCodePoint(codePoint)); // true		
	}
	
	//將文字轉成可以正常顯示的 HTML 編碼
	public static String convertSurrogatePairToHtmlCode(String str) {
		String htmlCode = "";
		
		StringBuilder htmlCodeStringBuilder = new StringBuilder();
		
		str.codePoints().forEach((int codePoint) -> {
			if (Character.isSupplementaryCodePoint(codePoint)) {
				htmlCodeStringBuilder.append(String.format("&#%d;", codePoint));
			} else {
				htmlCodeStringBuilder.append(org.apache.commons.text.StringEscapeUtils.escapeHtml4(Character.toString(codePoint)));
			}
		});
		
		htmlCode = htmlCodeStringBuilder.toString();
		return htmlCode;
	}
	
	//計算一個 Code Point 的 Surrogate Pair (代理對),
	//Surrogate Pair 含有兩部份,分別是 High Surrogate 和 Low Surrogate
	public static Map<String, Integer> calculateSurrogatePairFromCodePoint(int codePoint) {
		Map<String, Integer> surrogatePair = new HashMap<>();
		
		int s = codePoint - 0x10000;
		int highSurrogate = (s >> 10) + 0xd800;
		int lowSurrogate = (s & 0x3ff) + 0xdc00;
		
		surrogatePair.put("highSurrogate", highSurrogate);
		surrogatePair.put("lowSurrogate", lowSurrogate);
		
		return surrogatePair;
	}
	
	//計算一組 High Surrogate 和 Low Surrogate 代表的 Code Point 
	public static int calculateCodePointFromSurrogatePair(int highSurrogate, int lowSurrogate) {

		return ((highSurrogate - 0xd800) << 10) + (lowSurrogate - 0xdc00) + 0x10000;
	}
}

推薦一個線上有人寫的小工具,作者解釋了 Surrogate Pair 並寫了線上幫忙互轉 Code Point 和 Surrogate Pair 的功能:
Surrogate Pair Calculator etc.

參考資料:

  1. The Surrogate Pair Calculator etc.
  2. Unicode Surrogate Pairs
  3. Emoji character sequence &#55357;&#56391; breaks old XML process
  4. How to find the surrogate pair of a symbol in java

2024年2月15日 星期四

Java EE Spring JDBC tmeplate 執行 PostgreSQL 的 Function

這篇要來紀錄如何使用 Java EE Spring JDBC template 來執行
PostgreSQL 的 Function。

首先我們先建立一個測試用的 Table:

兩個 PostgreSQL Function,如以下 SQL 語法:
CREATE TABLE IF NOT EXISTS public.testTable
(
    id integer NOT NULL,
	title text NOT NULL,
	PRIMARY KEY(id) 
)
並且塞入一些資料:
INSERT INTO public.testTable(id, title) VALUES(1, '111');
INSERT INTO public.testTable(id, title) VALUES(2, '222');
INSERT INTO public.testTable(id, title) VALUES(3, '333');
然後建立三個 Function,以下三個 Function 的作用基本一樣,都是接受傳入的 param_id 變數,然後進行
SELECT * FROM testTable WHERE id = param_id
的查詢,不同的是傳回的 type 不同,呼叫 Functoin 的方式也有所不同,
可以注意到的是,有兩個 Function 的名稱一樣 (都取名為 queryUsingReturnedRefcursor),不過 Function 簽名不同 (傳入參數不同),這是為了演示 PostgreSQL 能夠支持同名 Function 故意取名的,類似 Java 的 Function Overload:
  1. queryUsingReturnedSetof(param_id integer):
    CREATE OR REPLACE FUNCTION queryUsingReturnedSetof(param_id integer) RETURNS SETOF testTable AS $$
      SELECT * FROM testTable WHERE id = param_id;
    $$ LANGUAGE sql;
    
    說明:
    此 Function 傳回值 type 為 SETOF,相當於傳回 Table 的資料行集合,呼叫 Function 的方式為如下 SQL 語法:
    SELECT * FROM queryUsingReturnedSetof(2);
  2. queryUsingReturnedRefcursor(param_id integer):
    CREATE OR REPLACE FUNCTION queryUsingReturnedRefcursor(param_id integer) RETURNS refcursor AS $$
    DECLARE ref refcursor;
    BEGIN
      OPEN ref FOR SELECT * FROM testTable WHERE id = param_id;
      RETURN ref; 
    END;
    $$ LANGUAGE plpgsql;
    
    說明:
    此 Function 傳回值 type 為 refcursor,為一個 cursor ,不是 Table 的資料行集合,直接呼叫的話只會得到一個 cursor ,必須要配合 FETCH 指令才能遍歷每一行的資料,例如以下語法:
    SELECT queryUsingReturnedRefcursor(2); FETCH ALL IN "<unnamed portal 6>";
    其中,<unnamed portal 6> 是被回傳的 cursor 名稱,不過在實際上每次執行 Function 後回傳的 cursor name 是會變化的,每次都不一樣,
    而執行完 Function 後, cursor 就消失了,導致執行 FETCH 時會有
    ERROR: cursor "<unnamed portal xxx>" does not exist 
    的錯誤,所以上面那樣的語法沒辦法正確地得到查詢結果資料行,
    要解決上面的問題,可以看下一個 Functoin 的說明,
    其想法是把想要的 cursor name 傳進 Function 以指定回傳的 cursor name。

    不過如果是用 Java 去呼叫的話,是可以成功執行並得到資料行的,
    之後可以看到我們可以用 Java 去指定 cursor name。
  3. queryUsingReturnedRefcursor(ref refcursor, param_id integer):
    CREATE OR REPLACE FUNCTION queryUsingReturnedRefcursor(ref refcursor, param_id integer) RETURNS refcursor AS $$
    BEGIN
      OPEN ref FOR SELECT * FROM testTable WHERE id = param_id;
      RETURN ref; 
    END;
    $$ LANGUAGE plpgsql;
    
    說明:
    此 Function 傳回值 type 為 refcursor,跟上一個 Fucntion 一樣,只差在多傳進一個指定的 cursor name,這樣我們就可以用已知的 cursor name 來取得資料行,呼叫方式如下:
    SELECT queryUsingReturnedRefcursor('xxx_cur', 2); FETCH ALL IN "xxx_cur";
    其中 xxx_cur 可以隨意更換成想要的 cursor name。
接下來是 Java EE Spring 的部份了。
下圖為測試專案的檔案結構,紅框處的是主要專案檔案:






因為重要的地方主要是 DAO 使用 JdbcTemplate 的部份,
為了簡單起見,所以這裡只展示 DAO 的部份,也就是 com.dao.MyDAO.java,
其他 Java EE Spring、Data Source 、使用到的 lib (使用 Maven POM 檔設定) 等的設定可以參考文章最後的源碼下載分享

MyDAO.java :
package com.dao;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcCall;
import org.springframework.stereotype.Repository;

@Repository
public class MyDAO {

	@Autowired
	@Qualifier("postgresql_JdbcTemplate")
	private JdbcTemplate postgresql_JdbcTemplate;
	
	public List<Map> queryUsingReturnedSetof(int id) {
		List<Map> dataList = postgresql_JdbcTemplate.query("SELECT * FROM queryUsingReturnedSetof(?);", new RowMapper<Map>() {		
	        @Override
	        public Map mapRow(ResultSet rs, int rowNumber) throws SQLException {
	        	Map<String, Object> map = new HashMap<>();
	        	
	        	map.put("id", rs.getInt("id"));
	        	map.put("title", rs.getString("title"));
	        	
	            return map;
	        }
	    }, id);
		
		//for test
		for (Map data : dataList) {
			System.out.println(data.get("id") + ", " + data.get("title"));
		}
		
		return dataList;
	}
	
	public List<Map> queryUsingReturnedRefcursor(int id) {
		String customCursorName = "xxx_cur";
		
		SimpleJdbcCall simpleJdbcCall = new SimpleJdbcCall(postgresql_JdbcTemplate)
				                        .withProcedureName("queryUsingReturnedRefcursor")
				                        .declareParameters(new SqlParameter("id", Types.INTEGER))
				                        .withoutProcedureColumnMetaDataAccess()
				                        .returningResultSet(customCursorName, new RowMapper<Map>() {
				                	        @Override
				                	        public Map mapRow(ResultSet rs, int rowNumber) throws SQLException {
				                	        	Map<String, Object> map = new HashMap<>();
				                	        	
				                	        	map.put("id", rs.getInt("id"));
				                	        	map.put("title", rs.getInt("title"));
				                	        	
				                	            return map;
				                	        }
				                	    });
		
		Map returnedMapContainsCursorName = simpleJdbcCall.execute(new MapSqlParameterSource().addValue("id", id));
		List<Map> dataList = (List) returnedMapContainsCursorName.get(customCursorName);
		
		//for test
		for (Map data : dataList) {
			System.out.println(data.get("id") + ", " + data.get("title"));
		}
		
		return dataList;
	}
}

說明:
queryUsingReturnedSetof(int id) 對應到 PostgreSQL 中的 queryUsingReturnedSetof(param_id integer) 這個 Function。

queryUsingReturnedRefcursor(int id) 對應到 PostgreSQL 中的 queryUsingReturnedRefcursor(param_id integer) 這個 Function,可以看到它可以讓我們指定一個回傳的 refCursor name,
simpleJdbcCall.execute() 並不直接回傳 data list 資料,而是一個 Map,
就像上面我們直接用 Sql 語法查詢那樣回傳的 refCursor 的資料,
我們需要再用指定的 refCursor name 當作 Key 從 Map 中才能獲取真正的 data list 資料。

源碼下載分享: