'use strict';
/* common0.js の簡略版:
charmod-ja.html
RFC6454-ja.html
RFC6901-ja.html
RFC6902-ja.html
w3c-common-ja.html
*/
// 要素取得
function E(id){
// const e = document.getElementById(id);if(!e) {console.log(id);};return e;
return document.getElementById(id);
}
// 要素作成
function C(tag){
return tag ?
document.createElement(tag) :
document.createDocumentFragment();
}
function EMPTY_FUNC(){}
// セレクタ対象の要素を反復処理
function repeat(selector, callback, root){
if(!root) root = document
const elements = root.querySelectorAll(selector);
const L = elements.length;
for(let i = 0; i < L; i++){
callback(elements[i]);
}
}
const PAGE_DATA = Object.create(null);
/* possible members:
options:
see common1.js
ref_normative:
ref_informative:
参照文献データ
original_id_map:
訳文 id → 原文 id
spec_metadata
仕様メタデータ
words_table:
単語対応
*/
const COMMON_DATA = Object.create(null);
// 予約済みメンバ
const Util = {
_COMP_: null,
DEFERRED: [], // 遅延実行
initAdditional: EMPTY_FUNC,
getState: EMPTY_FUNC, // 状態保存
setState: EMPTY_FUNC,
get_mapping: EMPTY_FUNC,
getDataByLevel: EMPTY_FUNC,
get_header: EMPTY_FUNC,
dump: EMPTY_FUNC,
ready: EMPTY_FUNC,
rebuildToc: EMPTY_FUNC,
buildTocList: EMPTY_FUNC,
parseSourceBlock: EMPTY_FUNC,
// common0a.js
word_switcher: null,
// common1.js
ADDITIONAL_NODES: [],
// CONTROL_UI: C(), //追加 UI
CLICK_HANDLERS: {},
removeAdditionalNodes: EMPTY_FUNC,
indexHide: EMPTY_FUNC,
dfnHide: EMPTY_FUNC,
dfnInit: EMPTY_FUNC,
altLinkInit: EMPTY_FUNC,
fillMisc: EMPTY_FUNC,
toggleSource: EMPTY_FUNC,
indexInit: EMPTY_FUNC,
switchView: EMPTY_FUNC,
ref_position: null,
page_state: Object.create(null),
saveStorage: EMPTY_FUNC,
// supportsListenerOptions: false,
XXXXX: EMPTY_FUNC
};
// 改行/コロン区切りの文字列データから連想配列を取得
Util.get_mapping = function(data, map){
map = map || Object.create(null);
const rxp = /\n(\S.*?):(.*)/g
let m;
while(m = rxp.exec(data)){
map[m[1]] = m[2];
}
return map;
};
// ●●区切りの文字列から有名データブロックを抽出
Util.parseBlocks = function(source){
// const rxp = RegExp('(\n' + splitter + ').+');
const result = Object.create(null);
let name = '';
source.split(/(\n●●.*)/).forEach(function(block){
if(block.slice(0,3) === '\n●●'){
name = block.slice(3);
if(!name){
// 無名ブロックはコメント
return;
}
if(!(name in result)){
result[name] = '';
}
}else if(name){
result[name] += block;
}
});
return result;
}
/*
'token:word1:word2:word3:...' の形式の各行を
'token:word' の形に変換 ( word = level 番目の word )
word === 空 の場合
word = 最も level が高い空でない word
word === '~' の場合
恒等置換
*/
Util.getDataByLevel = function(data, level){
return data
// スペース/タブ開始行を削除
.replace(/\n[ \t].*/g, '')
// ":" 区切りの行フィールド数を level + 1 個に圧縮
// level = 2 の場合: /((:[^:\n]*){3}).*/g ( w:w1:w2:w3 => w:w1:w2 )
.replace(RegExp('((:[^:\\n]*){' + ((level & 0xF) + 1) + '}).*', 'g'), '$1')
// 省略されたフィールドを補填 ( …w::\n => w:w\n)
.replace(/([^:\n]*):+(?=\n)/g, '$1:$1')
// 合間のフィールドをカット
.replace(/:.*:/g, ':')
// token が '-' で終端している場合 token:( token から末尾の '-' を除去した結果 ) に置換
// .replace(/^(.*)-:~$/mg, '$1-:$1')
// "~" への map (恒等置換 指示)は削除
.replace(/^.*:~$/mg, '')
;
};
// 節見出しを取得
Util.get_header = function(section){
const header = section && section.firstElementChild;
return (
header && /^H\d$/.test(header.tagName)
)? header : null;
};
Util.getState = function(key, default_val, type){
if(! (key in Util.page_state) ) return default_val;
const val = Util.page_state[key];
return (type && (typeof(val) !== type))? default_val : val;
};
Util.setState = function(key, val){
const page_state = Util.page_state;
const old_val = page_state[key];
if(val === old_val) return;
if(val === undefined){
delete page_state[key];
} else {
page_state[key] = val;
}
history.replaceState( page_state, '' );
Util.saveStorage(page_state);
};
new function(){
if(!window.console){
window.console = { log: EMPTY_FUNC };
}
}
new function(){
// meta with viewport for mbile (ideally, should be set by CSS, not meta tag)
const head = document.head || document.getElementsByTagName('head')[0];
if(!head) return;
// const w = screen.width;...
const meta = C('meta');
meta.setAttribute('name', 'viewport');
meta.setAttribute('content', 'width=device-width, initial-scale=1, shrink-to-fit=no');
head.appendChild(meta);
}
new function(){
Util._COMP_ = new Promise(function(resolve){
document.addEventListener('DOMContentLoaded', function(){
init();
resolve();
}, false);
});
// 初期化
function init(){
document.removeEventListener('DOMContentLoaded', init, false);
const elem = E('_source_data');
if(elem){
Object.assign(PAGE_DATA, Util.parseBlocks(elem.textContent));
elem.parentNode.removeChild(elem);
}
const options =
PAGE_DATA.options = Util.get_mapping(PAGE_DATA.options || '');
// 利用者 表示設定
let page_state = (JSON && get_state()) // setup saveStorage
Util.page_state = page_state = history.state || page_state || Util.page_state;
const classList = document.body.classList;
if(page_state.show_original){
classList.toggle('show-original');
}
if(page_state.side_menu){
classList.toggle('side-menu');
}
if(classList.contains('_expanded')){
// ページは展開状態で保存されている
options.expanded = true;
repeat('._hide_if_expanded', function(e){
e.style.display = 'none';
});
} else {
Util.ready();
classList.add('_expanded');
if(options.toc){
// 目次構築
const toc_list = Util.buildTocList(E(options.main));
toc_list.id = '_toc_list';
E(options.toc).appendChild(toc_list);
}
}
}
// 表示状態を sessionStorage から読み込む
function get_state(){
let page_state = null;
let storage_key = null;
storage_key = PAGE_DATA.options.page_state_key || window.location.pathname;
try {
// sessionStorage property へのアクセスのみでも security error になることがある
page_state = sessionStorage.getItem(storage_key);
Util.saveStorage = function(data){
sessionStorage.setItem(storage_key, JSON.stringify(data));
};
if(! page_state || (page_state.length > 1000)) return;
page_state = JSON.parse(page_state);
} catch(e){
console.log(e.message + ' failed sessionStorage.getItem');
}
if(page_state instanceof Object){
return page_state;
}
}
}
/** 目次構築 see common0.js
*/
Util.rebuildToc = function(main_id, list_id){
list_id = list_id || '_toc_list';
const toc_list = E(list_id), main = E(main_id);
if(toc_list && main) {
const new_list = Util.buildTocList(main);
new_list.id = list_id;
toc_list.parentNode.replaceChild(new_list, toc_list);
}
return toc_list;
}
Util.buildTocList = function(root){
const range = document.createRange();
const toc = buildToc(root);
if(toc) { // a 要素の入れ子を除去
repeat('a a', function(e){
range.selectNodeContents(e);
e.parentNode.replaceChild(range.extractContents(), e);
}, toc);
repeat('[id]', function(e){ // 重複 id を除去
e.removeAttribute('id');
}, toc);
}
return toc;
function buildToc(root){
let list = null;
for(let section = root.firstElementChild;
section;
section = section.nextElementSibling
){
if('SECTION' !== section.tagName) continue;
const header = Util.get_header(section);
if(!header) continue;
const id = section.id || header.id;
if(!id) continue;
const a = C('a');
a.href = '#' + id;
range.selectNodeContents(header);
a.appendChild(range.cloneContents());
const li = C('li');
li.appendChild(a);
const child_list = buildToc(section);
if(child_list) li.appendChild(child_list);
if(!list) list = C('ol');
list.appendChild(li);
}
return list;
}
}
/** 語彙切替 (訳語 → 原語 変換)
検索パタン
(1) 漢字+
動詞的/名詞的 用法は、[さしすせ]が後続しているかどうかで区別
例:
接続(する) → connection / connect(する)
(課題)「〜できる/〜可能」の場合も動詞的用法(例:断片化)
「動く」のような活用形は語尾も含めて変換する必要があるので
(3) の「漢字かな」の対象になる
(2) 漢字+
重複の区別/無変換指示のため、ドットの部分で検索キーを分岐
例:
指定 → specify / designate
概念 → notion / concept
取得 → fetch / retrieve / obtain
ドットの部分が '0' ならば無変換を指示
(3) 漢字+かな+
例:予約済み → reserved
ドットは意味を持たない
(5) カナ+
(4) 漢字+カナ+
例:下位プロトコル → subprotocol
(6) カナ+漢字+
例: "サービス供与" → serve, "リソース名" → resource name
(課題)
例:
割り当てる → allocate する
あてがう → assign する
呼び出す → invoke する
呼び出さない → invoke しない
ページ遷移(する) → navigation / navigate
待ち行列 → queue
<漢字><かな>(?=<漢字>) を複合 処理の対象にする?
[一-龠][ぁ-ゔ]
[\u4E00-\u9FFF] : CJK [一-鿆]
[\u30A1-\u30FC] : カナ [ァ-ー] // "・" も含まれる
[\u3041-\u3094] : かな [ぁ-ゔ]
*/
Util.word_switcher = {
rxp:
/(?:(?:[\u30A1-\u30FC]+|[\u4E00-\u9FFF]+)([\u3041-\u3094]*)?)(?=(?:([\u30A1-\u30FC]+|[\u4E00-\u9FFF]+)|([さしすせ]|でき))?)/g,
main_id: null,
html: null,
convert:function(map){
const main = E(this.main_id);
if(!this.html) this.html = main.innerHTML;
let ignore_flag = false;
main.innerHTML = this.html.replace(this.rxp, function(key, hira, suffix, sa){
if(ignore_flag){
ignore_flag = false;
return (hira || '');
}
let val;
if(hira){
//(カナ+|漢字+)かな*
//この場合は suffix, sa を無視
let opt = hira.slice(-4,-3);
key = key.slice(0,-8);
if(opt === '0'){
return key; // 無変換
}
if(hira.length > 8){
val = map[key];
} else {
val = map[key + opt];
}
} else if(sa){
if(key.charCodeAt(0) >= 0x4E00){
// サ行変格
val = map[key + '-'];
}
} else if(suffix){
val = map[key + suffix];
if(val) { // 漢字+カナ 複合語
ignore_flag = true;
}
}
val = val || map[key];
return val? (
(val.charCodeAt(0) < 128 ? ' ': '')
+ val +
(val.charCodeAt(val.length - 1) < 128 ? ' ': '')
) : key;
});
Util.rebuildToc(this.main_id);
},
revert: function(){
if(!this.html) return;
E(this.main_id).innerHTML = this.html;
this.html = null;
Util.rebuildToc(this.main_id);
},
// 2 レベル切替用
init_toggle: function(){
this.main_id = PAGE_DATA.options.main;
E('_view_control').insertAdjacentHTML( 'beforeend',
''
);
const that = this;
Util.CLICK_HANDLERS._toggle_words = function(){
Util.switchView(toggleWords2, true);
}
function toggleWords2(){
if(that.html){
that.revert();
} else {
that.convert(
Util.get_mapping(PAGE_DATA.words_table)
);
}
E('_toggle_words').className = that.html? '_converted' : '';
}
},
// 3 レベル以上用
num_levels: 3,
level:0,
switchWords: function(level){
if(isNaN(level)){
level = (this.level + 1);
}
level = (level & 0xF) % this.num_levels;
if(level === this.level) return;
this.level = level;
let mapping;
if(level > 0){
mapping =
Util.getDataByLevel(PAGE_DATA.words_table, level - 1 )
// 恒等置換 削除
.replace(/^([^\n:]+)[\d\-]?:\1$/mg, '');
mapping = Util.get_mapping(mapping);
}
const that = this;
Util.switchView(function(){
level ?
that.convert(mapping) :
that.revert();
}, true);
}
};