プログラマ38の日記

主にプログラムメモです。

Salesforce: PDFで日本語で折り返しを行う

SalesforceのPDF出力で日本語を表示すると折り返しがされなくて、レイアウトが崩れてしまう現象があります。

 

結論としては、日本語の文字と文字の間にwbrタグを入れることで対処できます。

 

次がhtmlのwbrタグを入れる前と入れた後での表示の違いになります。

入れる前と入れた後でその他のhtmlタグは同じです。

f:id:crmprogrammer38:20170331151920p:plain

上記の通り、wbrタグを入れる前は、日本語の部分がレイアウトが崩れているのがわかります。ですが、wbrタグを入れた後は、折り返しされてレイアウトも崩れていません。

 

以下wbrを入れるソースコードです。

 文字と文字の間にwbrを入れると共に、次の文字「"<" ">" "&" "半角スペース"」のエスケープや改行コードからbrタグへの変換を行っています。

また、wbrが不要な半角文字にはwbrを入れていないため、元々の折り返しにまかせています。(英単語の間で改行はされないようにしています)

 

[ソースコード]

 //wbrタグをいれつつ、エスケープする文字を対応する。
//エスケープは < > & 半角スペースとする。
public String insWBR(String value){
if( String.isempty(value) ){
return '';
}

String retstr = '';
for(integer i=0; i< value.length() ; i++ ){
if( i > 0 ){
if( value.substring(i-1, i).isAsciiPrintable()
|| ( value.substring(i-1, i).codepointAt(0) >= 55296
&& value.substring(i-1, i).codepointAt(0) <= 56319 ) ){
if( ' ' == value.substring(i-1, i) ){
retstr += '<wbr/>';
}
} else {
retstr += '<wbr/>';
}
}

String charval = value.substring(i, i+1);
if( charval == '\n' ){
retstr += '<br/>';
} else if( charval == '&' ){
retstr += '&amp;';
} else if( charval == ' ' ){
retstr += '&nbsp;';
} else if( charval == '>' ){
retstr += '&gt;';
} else if( charval == '<' ){
retstr += '&lt;';
} else {
retstr += charval ;
}
}

return retstr;
}

もちろん、htmlタグを含んだ文字列を表示するので、escape="false"の指定が必要です。( <apex:outputtext value="{!wbrvalue}" escape="false"/> )

 

2017/4/3 変更 サロゲートペアの文字の場合?となってしまうので、サロゲートペアの上位の後はタグを挿入しないように修正しました。

2017/7/24 サロゲートペアの上位文字は固定値ではなく、範囲で判定しなければいけないことに気がつき修正しました。

Salesforce: 項目ID(初期値設定のパラメータ)の一覧を取得する。

エンティティ定義(EntityDefinition)の子オブジェクトに、項目定義(FieldDefinition )があります。項目定義(FieldDefinition )をselectすると、編集画面で初期値をセットする時に使う項目IDが取得できます。

 

Select
EntityDefinitionId
,DurableId
,QualifiedApiName
,Label
,Length
,DataType
From
FieldDefinition
where EntityDefinition.QualifiedApiName='Opportunity'

 取得するとこんな結果がとれます。

f:id:crmprogrammer38:20170330175419p:plain

取得結果の"DurableId"のピリオド以降が項目IDです。

新規ボタンクリック後の編集画面や、編集ボタンクリック後の編集画面で、初期値をセットする時に、URLパラメータで項目IDを指定すると初期値がセットできますが、項目IDを動的に取ることができます。(参照関係、主従関係の項目IDの頭には、"CF"はついていないので、自分でつける必要があります)

 

※そもそもURLパラメータで項目IDを使って初期値をセットすることが正式にサポートされているやり方ではないのですが、使わざるをえない場面もあると思います。

環境依存の項目IDが多いとリリース時の作業が大変なので動的に項目IDを取得することで多少は楽になるかなと思います。

 

注意点としては、

APIバージョンは33以上で使えます。

・項目レベルセキュリティが設定されてないとselect結果に含まれません。

・必ずオブジェクトの検索条件が必要です。(一度に複数オブジェクトの検索が可能です)

項目定義(FieldDefinition )には他にも便利な項目がたくさんありますが、一部オブジェクトで、ServiceDataTypeId , ValueTypeId をselectすると内部エラーが発生します。うっかり全項目selectするとはまります。
(今のところContactとOpportunityでエラーとなりました)

 

Salesforce: オブジェクトのキープレフィックス(KeyPrefix:IDの頭3桁)の一覧を取得する方法

Salesforceの開発をしているとオブジェクトを特定する3文字のKeyPrefixが必要な時があります。自分のよく使う方法を書こうと思います。

 

1.オブジェクト情報の格納しているオブジェクトをselectする。

オブジェクト情報は、エンティティ定義(EntityDefinition)に格納されています。

一覧で取得するのにSOQLを実行するのみなので、一番シンプルな方法です。

例えば、次のSOQLで、オブジェクトのAPI名、表示ラベル、KeyPrefixが取得できます。

 Select
QualifiedApiName
,Label
,KeyPrefix
From
EntityDefinition

この方法だと、DataLoaderなどでも取得できます。エンティティ定義(EntityDefinition)には、その他にも項目があるので使いやすいと思います。

 

使用する際には次を注意しておく必要があります。

・エンティティ定義(EntityDefinition)はAPIバージョンは33以上で使えます。

・SOQLの発行をするので、Apex内でSOQLの発行件数にカウントされます。

・キープレフィックスは一意ではない。キープレフィックスが指定されていないオブジェクトの他に、項目変更履歴の履歴オブジェクトなど同一のキープレフィックスのものがあります。

・プロファイル(および権限セット)で参照以上の権限がついてないとSOQLの結果に含まれない。

 

2.ApexのdescribeGlobalを使う

ApexやAPIで用意されているdescribeGlobalを使います。コードを書かないといけないですが、SOQLは発行しないのが良い点だと思います。

Map<String, Schema.SObjectType> ret = Schema.getGlobalDescribe();

for(String key : ret.keySet() ){

Schema.DescribeSObjectResult[] descResult = Schema.describeSObjects(new String[]{key});
System.debug(key);
System.debug(descResult[0].getKeyPrefix());
System.debug(descResult[0].getLabel());
}

注意事項はSOQL発行の時と一部かぶります。

・キープレフィックスは一意ではない。キープレフィックスが指定されていないオブジェクトの他に、項目変更履歴の履歴オブジェクトなど同一のキープレフィックスのものがあります。

・プロファイル(および権限セット)で参照以上の権限がついてないとSOQLの結果に含まれない。

 

 

 

Excel: セルの値を数式で比較する時に、=(比較) と exact関数の違いではまったこと

私はプログラマーなので、プログラムの処理結果を期待値と比較する作業をかなりの頻度で行います。

 

そういう作業ではExcelのセルとセルの値を比較するのですが、

=( セル1 = セル2 )  での比較と、 = exact( セル1 , セル2 ) の比較で挙動が違いはまりました。

f:id:crmprogrammer38:20170309173532p:plain

 

#3では、=( セル1 = セル2 )  で比較すると数値の0と文字列"0"は異なっています。

#5にいたっては、数値の0 と 空白(いわゆるnull)が=( セル1 = セル2 )  で比較すると同じになってしまいます。

#6だと、=( セル1 = セル2 )  で比較だと大文字の小文字を区別しません。

 

数値の0と空白が同じ扱いなのは困ることが多いので、めんどくさくてもexact関数を使っていこうと思いました。

 

Excel: VBAのround関数ではまったこと

まとまった計算をするのにVBAは便利なので、ちょっとした時に書きます。

 

ですが、がっつりVBAで開発すること自体は少ないので細かなところではまりました。

 

はまったのは round 関数

数式のround関数はround_half_upなのですが、

VBAのround関数はround_half_evenなのです。

 

VBA内で数式のroundと同じ計算をしたい場合は、WorksheetFunction.Roundを使うことになります。数式の間隔で関数を使う場合はWorksheetFunctionを常に使っていったほうがいいのかなと思いました。

[計算結果]

f:id:crmprogrammer38:20170302170608p:plain

vbaのroundは、roundする桁の左側の数値が偶数の場合は切り下げになります。

意外と気が付きずらいのがさらにつらいです。

TERADATA: BIツールではViewで定義しよう

まずTERADATAでは、次のようにViewを定義します。

REPLACE VIEW
SampleView (
field1
,field2
,field3
) as
LOCKING TABLE SampleTable FOR ACCESS
select
field1
,field2
,field3
from
SampleTable;

上の赤の太字の記載は、テーブルがロックされててもデータを読むという指定です。

いわゆるダーティリードですが、DWHでは、1つのレコードを頻繁に更新することはないので、ダーティリードで特に問題になるケースは少ないと思います。

どちらかというと、ロックされるとデータが読めない方が問題があって、日中に時間のかかる更新を行っていると、その間selectが実行できなくなります。

※TERADATAのロック制御はTERADATA社のサイトに詳しく書いてあります。

 

大体のBIツールではselect文は自動で生成されるため、select文の頭に、locking table ・・・ for access を指定できません。そのためテーブルに対応するViewを定義し、Viewの中でlocking table ・・・ for accessを指定します。

そのViewをBIツールで定義することでロックされててもテーブルを読むことができるようになります。

DWH: データモデル(9.1から8にとらわれすぎない。)

この記事は、前回書いたデータモデリングの9つのことのラストです。

 

crmprogrammer38.hatenablog.com

 

思うことを書いてきましたが、データモデルのシンプルでわかりやすいガイドラインとしてスタースキーマモデルがあると思います。

 

1つ1つ場合分けをして、このときはこう、このときはこうという風に決めていくのではなく、まずスタースキーマモデルを作成して、その後まだ工夫の余地があるなら改善していくのがいいのではないかなと思います。

 

もちろん、最終的には業務要件を満たす(どうしても満たせない場合は要件の落としどころを探らないといけませんが)ことが目的なので、スタースキーマをベースにちょっとずつアレンジしていくことになります。

 

スタースキーマの定義から外れるからダメとか、完全に正規化してスノーフレークモデルにしないとダメとかそういうことではないので、ルールに縛られすぎずにモデリングしていきたいものです。