第058回_一般実体テーブル作成処理の実装2
»
MakingGeTableVisitorの実装を紹介する前にVisitorの設計を明確化するために少し工夫をします。
同一構文・異なる処理に対する工夫
今回処理を記載する部分の構文は次の範囲です。GEDecl ::= Name S GeEntityDef SYMBOL ('>')
GeEntityDef ::= GeInternalDef | GeExternalDef
GeInternalDef ::= EntityValue S?
GeExternalDef ::= ExternalID NDataDecl?
NDataDecl ::= S ('NDATA' S Name S?)?
ここで、
Name
EntityValue
ExternalID
は他の構文にも出てくる内容です。
# NameはGEDeclにもNDataDeclにも出てくるので判り易いです。
これらは、同じnodeのまま扱うと出てくる位置による処理の違いを表現できません。
-背景
-解決案1 それぞれのExternalIDと子ノードの非終端と処理が必要なnodeを別のnodeとして表現する
-解決案2 お互いのVisitorの処理範囲が被らないようにする
-解決案2改 構文の共通部分でデータ生成が同じなら1つのVisitorで実装する
例えばExternalIDは
のように4か所に出てきて、それぞれのExternalIDにて処理が異なります。
出現箇所 | 意味と処理内容 |
doctypedecl | 外部サブセットのURIを表現し、doctypedecl内で内部サブセットを読み込んだ後に外部サブセットを読み込む |
EntityDef | 外部一般実体のURIを表現し、URIを一般実体テーブルに保存する |
PEDef | 外部パラメータ実体のURIを表現し、URIをパラメータ実体テーブルに保存する |
NotationDecl | アプリケーション側が利用する何等かのURIを表現し、記法テーブルに保存する |
のように4か所に出てきて、それぞれのExternalIDにて処理が異なります。
-解決案1 それぞれのExternalIDと子ノードの非終端と処理が必要なnodeを別のnodeとして表現する
例えば、
GeExternalDef ::= GeExternalID NDataDecl?
GeExternalID ::= GeSystemLiteralOfExternalId | GePubidLiteralOfExternalId
GeSystemLiteralOfExternalId ::= 'SYSTEM' S GeSystemLiteral
GePubidLiteralOfExternalId ::= 'PUBLIC' S GePubidLiteral S GeSystemLiteral
GeSystemLiteral ::= LITERALトークン
GePubidLiteral ::= LITERAL (PubidChar*)
と
PEDef ::= EntityValue PeExternalID
PeExternalID ::= PeSystemLiteralOfExternalId | PePubidLiteralOfExternalId
PeSystemLiteralOfExternalId ::= 'SYSTEM' S PeSystemLiteral
PePubidLiteralOfExternalId ::= 'PUBLIC' S PePubidLiteral S PeSystemLiteral
PeSystemLiteral ::= LITERALトークン
PePubidLiteral ::= LITERAL (PubidChar*) とすれば、GeExternalDefとPEDefにあるExternalIDは同一の構文ではなくなるので異なる処理を書くことができます。ただし、この方針を採用するとEbnfXXXNodeクラスは同一の処理でも同一構文の共有ができなくなります。
案1はnodeクラスの肥大化が懸念となります。自動生成の場合は案1を採用して問題ありません。自動生成でない場合はVisitorで対処した案2以降を考える必要が出てきます。
GeExternalDef ::= GeExternalID NDataDecl?
GeExternalID ::= GeSystemLiteralOfExternalId | GePubidLiteralOfExternalId
GeSystemLiteralOfExternalId ::= 'SYSTEM' S GeSystemLiteral
GePubidLiteralOfExternalId ::= 'PUBLIC' S GePubidLiteral S GeSystemLiteral
GeSystemLiteral ::= LITERALトークン
GePubidLiteral ::= LITERAL (PubidChar*)
と
PEDef ::= EntityValue PeExternalID
PeExternalID ::= PeSystemLiteralOfExternalId | PePubidLiteralOfExternalId
PeSystemLiteralOfExternalId ::= 'SYSTEM' S PeSystemLiteral
PePubidLiteralOfExternalId ::= 'PUBLIC' S PePubidLiteral S PeSystemLiteral
PeSystemLiteral ::= LITERALトークン
PePubidLiteral ::= LITERAL (PubidChar*) とすれば、GeExternalDefとPEDefにあるExternalIDは同一の構文ではなくなるので異なる処理を書くことができます。ただし、この方針を採用するとEbnfXXXNodeクラスは同一の処理でも同一構文の共有ができなくなります。
案1はnodeクラスの肥大化が懸念となります。自動生成の場合は案1を採用して問題ありません。自動生成でない場合はVisitorで対処した案2以降を考える必要が出てきます。
-解決案2 お互いのVisitorの処理範囲が被らないようにする
doctypedeclを処理するVisitor
EntityDefを処理するVisitor
PEDefを処理するVisitor
NotationDeclを処理するVisitor
のお互いの処理範囲が被らなければ各Visitorが異なる処理を書いても問題ありません。構文階層で言えば、doctypedeclの中のマークアップ宣言である、GEDeclとPEDeclとNotationDeclは、それぞれ異なるVisitorで表現するだけで処理範囲は被らなくなります。doctypedeclはGEDeclとPEDeclとNotationDeclを含む範囲の構文ですが、各構文を異なるVisitorとして呼び出すようにすれば同じnodeであったとしても正しく機能します。
案2の調整が上手くいかない場合は案1に戻りますが、ExternalIDの処理の場合は案2で解決できます。ただし、構文設計者の意図を考えると、ExternalIDのデータを取得する処理はほぼ同じなので、この部分を共通化する方法も考えましょう。
EntityDefを処理するVisitor
PEDefを処理するVisitor
NotationDeclを処理するVisitor
のお互いの処理範囲が被らなければ各Visitorが異なる処理を書いても問題ありません。構文階層で言えば、doctypedeclの中のマークアップ宣言である、GEDeclとPEDeclとNotationDeclは、それぞれ異なるVisitorで表現するだけで処理範囲は被らなくなります。doctypedeclはGEDeclとPEDeclとNotationDeclを含む範囲の構文ですが、各構文を異なるVisitorとして呼び出すようにすれば同じnodeであったとしても正しく機能します。
案2の調整が上手くいかない場合は案1に戻りますが、ExternalIDの処理の場合は案2で解決できます。ただし、構文設計者の意図を考えると、ExternalIDのデータを取得する処理はほぼ同じなので、この部分を共通化する方法も考えましょう。
-解決案2改 構文の共通部分でデータ生成が同じなら1つのVisitorで実装する
(フィールドにPubidLiteralとSystemLiteralを持つ)ExernalIDというデータクラスを作ることを考える場合、
doctypedeclを処理するVisitor
EntityDefを処理するVisitor
PEDefを処理するVisitor
NotationDeclを処理するVisitor
では全く同じ処理ですから、このためのMakingExternalIDVisitorを用意すると共通化できます。
ただし、それぞれの異なる処理部分を
doctypedeclを処理するVisitor
EntityDefを処理するVisitor
PEDefを処理するVisitor
NotationDeclを処理するVisitor
側で実装するためのラッパーノードが必要になります。例えば、
GeExternalDef ::= GeExternalID NDataDecl?
GeExternalID (継承 ExternalID)
とすることで、
ExternalIDは案2改で実装することができます。
doctypedeclを処理するVisitor
EntityDefを処理するVisitor
PEDefを処理するVisitor
NotationDeclを処理するVisitor
では全く同じ処理ですから、このためのMakingExternalIDVisitorを用意すると共通化できます。
ただし、それぞれの異なる処理部分を
doctypedeclを処理するVisitor
EntityDefを処理するVisitor
PEDefを処理するVisitor
NotationDeclを処理するVisitor
側で実装するためのラッパーノードが必要になります。例えば、
GeExternalDef ::= GeExternalID NDataDecl?
GeExternalID (継承 ExternalID)
とすることで、
MakingGeTableVisitor#visit(EbnfGeExternalIDNode n)
{
MakingExternalIDVisitor v = new MakingExternalIDVisitor();
//自動的にvisit(EbnfExtenralIDNode n)として処理する
n.accept(v);
m_result = v.getResult();
if(m_result)
{
m_externalID = v.getExtenralID();
//個別の処理:一般実体テーブルに保存する
}
}
■Name
Nameの考え方もExternalIDのように考えることができます、
ただし、
単一のNodeであること、
Nameは完全なデータ構造だけで処理を持たないこと
を考慮すると少しだけ事情が変わります。 まず、案1のように実装することはノードの増加を招く以外には問題がありません。
次に、案2のように実装しようとしても単一ノードのため意味がありません。
しかし、Nameの親構文(Nameを含む構文)がたった1つのNameだけをデータとして扱う場合は案2を採用する意味が
出てきます。Nameが出てくる構文の内、処理が必要になる構文は
[28] doctypedecl ::= '<!DOCTYPE' S Name (S ExternalID)? S? ('[' intSubset ']' S?)? '>'
[40] STag ::= '<' Name (S Attribute)* S? '>'
[41] Attribute ::= Name Eq AttValue
[44] EmptyElemTag ::= '<' Name (S Attribute)* S? '/>'
[48] cp ::= (Name | choice | seq) ('?' | '*' | '+')?
[51] Mixed ::= '(' S? '#PCDATA' (S? '|' S? Name)* S? ')*' | '(' S? '#PCDATA' S? ')'
[52] AttlistDecl ::= '<!ATTLIST' S Name AttDef* S? '>'
[53] AttDef ::= S Name S AttType S DefaultDecl
[58] NotationType ::= 'NOTATION' S '(' S? Name (S? '|' S? Name)* S? ')'
[68] EntityRef ::= '&' Name ';'
[69] PEReference ::= '%' Name ';'
[71] GEDecl ::= '<!ENTITY' S Name S EntityDef S? '>'
[72] PEDecl ::= ''<!ENTITY' S '%' S Name S PEDef S? ''>'
[76] NDataDecl ::= S 'NDATA' S Name
[82] NotationDecl ::= '<!NOTATION' S Name S (ExternalID | PublicID) S? '>'
で、その内で単独のNameだけをデータに持つ構文は、
[68] EntityRef ::= '&' Name ';'
[69] PEReference ::= '%' Name ';'
[76] NDataDecl ::= S 'NDATA' S Name
となります。ただし、NDataDecl自体も特別な処理がなく、実質、
GeExternalDef ::= ExternalID (S 'NDATA' S Name)?
であることを考慮すると、GeExternalDefはName以外にもExternalIDを持ち、案2改の適用範囲外となります。
結局、対象となる構文は、
[68] EntityRef ::= '&' Name ';'
[69] PEReference ::= '%' Name ';'
となります。つまり、GEDeclの子ノードについて考えた場合、
GEDecl ::= GeName S GeEntityDef SYMBOL ('>')
GeName(Nameを継承)
NDataDecl ::= S ('NDATA' S NDataName S?)?
NDataName(Nameを継承)
になりますが、
EntityRef ::= '&' Name ';'
PEReference ::= '%' Name ';'
についてはNameのままにしておきます。
ただし、
単一のNodeであること、
Nameは完全なデータ構造だけで処理を持たないこと
を考慮すると少しだけ事情が変わります。 まず、案1のように実装することはノードの増加を招く以外には問題がありません。
次に、案2のように実装しようとしても単一ノードのため意味がありません。
しかし、Nameの親構文(Nameを含む構文)がたった1つのNameだけをデータとして扱う場合は案2を採用する意味が
出てきます。Nameが出てくる構文の内、処理が必要になる構文は
[28] doctypedecl ::= '<!DOCTYPE' S Name (S ExternalID)? S? ('[' intSubset ']' S?)? '>'
[40] STag ::= '<' Name (S Attribute)* S? '>'
[41] Attribute ::= Name Eq AttValue
[44] EmptyElemTag ::= '<' Name (S Attribute)* S? '/>'
[48] cp ::= (Name | choice | seq) ('?' | '*' | '+')?
[51] Mixed ::= '(' S? '#PCDATA' (S? '|' S? Name)* S? ')*' | '(' S? '#PCDATA' S? ')'
[52] AttlistDecl ::= '<!ATTLIST' S Name AttDef* S? '>'
[53] AttDef ::= S Name S AttType S DefaultDecl
[58] NotationType ::= 'NOTATION' S '(' S? Name (S? '|' S? Name)* S? ')'
[68] EntityRef ::= '&' Name ';'
[69] PEReference ::= '%' Name ';'
[71] GEDecl ::= '<!ENTITY' S Name S EntityDef S? '>'
[72] PEDecl ::= ''<!ENTITY' S '%' S Name S PEDef S? ''>'
[76] NDataDecl ::= S 'NDATA' S Name
[82] NotationDecl ::= '<!NOTATION' S Name S (ExternalID | PublicID) S? '>'
で、その内で単独のNameだけをデータに持つ構文は、
[68] EntityRef ::= '&' Name ';'
[69] PEReference ::= '%' Name ';'
[76] NDataDecl ::= S 'NDATA' S Name
となります。ただし、NDataDecl自体も特別な処理がなく、実質、
GeExternalDef ::= ExternalID (S 'NDATA' S Name)?
であることを考慮すると、GeExternalDefはName以外にもExternalIDを持ち、案2改の適用範囲外となります。
結局、対象となる構文は、
[68] EntityRef ::= '&' Name ';'
[69] PEReference ::= '%' Name ';'
となります。つまり、GEDeclの子ノードについて考えた場合、
GEDecl ::= GeName S GeEntityDef SYMBOL ('>')
GeName(Nameを継承)
NDataDecl ::= S ('NDATA' S NDataName S?)?
NDataName(Nameを継承)
になりますが、
EntityRef ::= '&' Name ';'
PEReference ::= '%' Name ';'
についてはNameのままにしておきます。
■EntityValue
EntityValueはGEDeclとPEDeclで出現し処理も同じですので、Visitorを共通化する意味があります。ただし、他の包含関係があるので少し検討が必要です。
EntityValue ::= SYMBOL ('"') (NORMAL_TOKEN_B2 | PEReference | Reference)* SYMBOL ('"')
| SYMBOL ("'") (NORMAL_TOKEN_B1 | PEReference | Reference)* SYMBOL ("'")
PEReference ::= SYMBOL ('%') Name SYMBOL (';')
Reference ::= EntityRef | CharRef
EntityRef ::= SYMBOL ('&') Name SYMBOL (';')
CharRef ::= SYMBOL ('&#') WORD ([0-9]+) SYMBOL (';') | SYMBOL ('&#x') WORD ([0-9a-fA-F]+) ) SYMBOL (';')
の内、CharRefは出現位置が複数あっても処理が一緒です。一方PEReferenceとReferenceは、出現位置が複数あって且、処理が異なります。
-CharRefを処理する共通Visitorによる修正
-PEReference/Referenceを処理するための修正
EntityValue ::= SYMBOL ('"') (NORMAL_TOKEN_B2 | PEReference | Reference)* SYMBOL ('"')
| SYMBOL ("'") (NORMAL_TOKEN_B1 | PEReference | Reference)* SYMBOL ("'")
PEReference ::= SYMBOL ('%') Name SYMBOL (';')
Reference ::= EntityRef | CharRef
EntityRef ::= SYMBOL ('&') Name SYMBOL (';')
CharRef ::= SYMBOL ('&#') WORD ([0-9]+) SYMBOL (';') | SYMBOL ('&#x') WORD ([0-9a-fA-F]+) ) SYMBOL (';')
の内、CharRefは出現位置が複数あっても処理が一緒です。一方PEReferenceとReferenceは、出現位置が複数あって且、処理が異なります。
-CharRefを処理する共通Visitorによる修正
CharRefVisitorを使うために、EvCharRefを追加して
Reference ::= EntityRef | EvCharRef
EvCharRef(CharRefを継承)
とします。
Reference ::= EntityRef | EvCharRef
EvCharRef(CharRefを継承)
とします。
-PEReference/Referenceを処理するための修正
EvPEReference(PEReferenceを継承)
と
EvReference :: EvEntityRef | EvCharRef
EvEntityRef(EvEntityRefを継承)
を追加して、
EntityValue ::= SYMBOL ('"') (NORMAL_TOKEN_B2 | EvPEReference | EvReference)* SYMBOL ('"')
| SYMBOL ("'") (NORMAL_TOKEN_B1 | EvPEReference | EvReference)* SYMBOL ("'")
に修正します。
と
EvReference :: EvEntityRef | EvCharRef
EvEntityRef(EvEntityRefを継承)
を追加して、
EntityValue ::= SYMBOL ('"') (NORMAL_TOKEN_B2 | EvPEReference | EvReference)* SYMBOL ('"')
| SYMBOL ("'") (NORMAL_TOKEN_B1 | EvPEReference | EvReference)* SYMBOL ("'")
に修正します。
CharRefVisitorの実装
工夫の内容を元にCharRefVisitorを実装します。実装は次のようにできます。
public class CharRefVisitor extends SyntaxParserVisitor
{
//文字参照で示すコードポイントから参照する文字を得る変換器
private static CharRefConvertor m_charRefConvertor
= CharRefConvertor.getInstance();
private String m_decimalCharRef;
private String m_hexCharRef;
private String m_value;
public CharRefVisitor()
{
m_decimalCharRef = null;
m_hexCharRef = null;
m_value = "";
}
public String getValue()
{
return m_value;
}
@Override
public void visit(EbnfHexCharRefNode n)
{
super.visit(n);
if (m_result)
{
//巡回用データに文字列を追加する
m_value +=m_hexCharRef;
}
else
{
//何もしない
}
}
@Override
//Charef(&#x unicode ;) のunicode部分を文字に変換する
public void visit(EbnfHexCharRefCoreNode n)
{
Token token = m_tokenManager.nextToken();
m_result = token.match(n);
if(m_result)
{
//処理に成功したので巡回用データに登録する
String str = token.getString();
m_hexCharRef = m_charRefConvertor.convertOneHexUnicodeToString(str);
}
else
{
//何もしない
}
}
@Override
public void visit(EbnfDecimalCharRefNode n)
{
super.visit(n);
if (m_result)
{
//巡回用データに文字列を追加する
m_value+=m_decimalCharRef;
}
else
{
//何もしない
}
}
@Override
//Charef(&# unicode ;) のunicode部分を文字に変換する
public void visit(EbnfDecimalCharRefCoreNode n)
{
Token token = m_tokenManager.nextToken();
m_result = token.match(n);
if(m_result)
{
//処理に成功したので巡回用データに登録する
String str = token.getString();
m_decimalCharRef = m_charRefConvertor.convertOneUnicodeToString(str);
}
else
{
//何もしない
}
}
}
CharRefConvertorの実装は次のようになります。
public class CharRefConvertor
{
private static CharRefConvertor m_instance = new CharRefConvertor();
private final IntegerRangeKeyMap<Boolean> m_charMap = new IntegerRangeKeyMap<Boolean>();
private CharRefConvertor()
{
m_charMap.put('\u0001', '\uD7FF', true);
m_charMap.put('\uE000', '\uFFFD', true);
//サロゲートペアは未対応
m_charMap.setOutOfRange(false);
}
public static CharRefConvertor getInstance()
{
return m_instance;
}
//Charef(&# unicode;)のunicode部分を受け取って文字に戻す
public String convertOneUnicodeToString(String unicode)
{
int[] codePoints = new int[1];
codePoints[0] = new Integer(unicode).intValue();
String retStr = new String(codePoints, 0, 1);
//Charの範囲内か確認する
boolean isChar = this.isChar(retStr.charAt(0));
//Charの範囲内の場合
if (isChar)
{
//何もしない
}
//Charの範囲内でない場合
else
{
//TODO
// 整形式制約違反
//空文字にする
retStr = "";
}
return retStr;
}
//Charef(&#x unicode;)のunicode部分を受け取って文字に戻す
public String convertOneHexUnicodeToString(String unicode)
{
int[] codePoints = new int[1];
codePoints[0] = Integer.parseInt(unicode,16);
String retStr = new String(codePoints, 0, 1);
//Charの範囲内か確認する
boolean isChar = this.isChar(retStr.charAt(0));
//Charの範囲内の場合
if (isChar)
{
//何もしない
}
//Charの範囲内でない場合
else
{
//TODO
// 整形式制約違反
//空文字にする
retStr = "";
}
return retStr;
}
private boolean isChar(char txt)
{
return m_charMap.get(txt);
}
}
長くなったので次回に続きます。
コメント
コメントを投稿する
SpecialPR