この章では、2つのExtensible Markup Language (XML)入力間の差分を求め、差分を一方のXML文書に適用する方法について説明します。
内容は次のとおりです。
Oracle XmlDiff
を使用して、類似した2つのXML文書の差分を定義できます。XmlDiff
により差分を示すXdiff
インスタンス・ドキュメントが生成されます。Xdiff
インスタンス・ドキュメントは、XMLスキーマの1つであるXdiff
スキーマに準拠するXML文書です。
XmlPatch
を使用して、Xdiff
インスタンス・ドキュメントを受け取り、他の文書に変更を適用できます。このプロセスを使用して、同一の変更を多くのXML文書に適用できます。
XmlDiff
では、入力と出力にDocument Object Model (DOM)アプリケーション・プログラミング・インタフェース(API)のみがサポートされます。
XmlPatch
もまた、DOMの入力およびパッチ文書をサポートしています。
XmlDiff
およびXmlPatch
は、C APIまたはコマンドライン・ツールを介して使用できます。2つのStructured Query Language (SQL)関数によって公開されます。
XmlHash
C APIは、XMLツリーまたはサブツリーのハッシュ値を計算するために提供されます。2つのツリーまたはサブツリー間のハッシュ値が同数の場合、これらのツリーは非常に高い確率で同一です。
XmlDiff
を使用して、2つの入力文書を示すツリーを比較し差分を検出します。
この2つの入力文書では、同一のキャラクタ・セット・エンコーディングを使用する必要があります。Xdiff
(出力)インスタンス・ドキュメントには、入力文書のデータ・エンコーディング(DOMエンコーディング)と同一のエンコーディングが含まれます。
比較に関して、最適化と呼ばれる2つのオプションがあります。
グローバル最適化(デフォルト)
文書ツリー全体が比較されます。
ローカル最適化
兄弟関係のレベルで比較が行われます。ローカル最適化では、2つのツリーの対応する親の下にある兄弟関係が比較されます。
グローバル最適化では、大きな文書に対してより多くの時間および領域を使用できますが、生成されるのは常に最小の差分セット(最適差分)です。ローカル最適化の方が処理速度は速いですが、最適差分は生成されない場合があります。
一般的に、ハッシュによりグローバル最適化が早くなりますが、質が若干落ちる可能性もあります。ローカル最適化では、ハッシュにより差分出力の質が改善されます。異なるハッシュのレベルを使用すると、ローカル差分およびグローバル差分の両方が生成される可能性があります。
ローカルおよびグローバルの両方の最適化に関するハッシュの使用を指定できます。
ハッシュを指定するには、hashLevel
パラメータを入力します。hashLevel
が1より大きい場合、DOMHash
値を使用して、差分がdepth >= hashLevel
のすべてのサブツリーを比較できます。ハッシュ値が同一の場合は、サブツリーも同一と推定されます。
XmlDiff
は、比較の実行中、属性順に差分を無視します。
XmlDiff
はDocType宣言を無視します。ファイルはDocument Type Definition (DTD)に対して検証されません。
名前空間の接頭辞が同一の名前空間Universal Resource Identifier (URI)を参照する場合は、XmlDiff
は名前空間の接頭辞における差分を無視します。それ以外の場合で、2つのノードに同じローカル名および内容が含まれていて名前空間URIが異なる場合は、差分が明示されます。
注意:
XmlDiff
は入力文書上で、スキーマに基づかない方法で動作します。要素または属性上で、型を認識する方法では動作しません。
表23-1に、コマンドライン・オプションを示します。
表23-1 C言語に関するXmlDiffコマンドライン・オプション
オプション | 説明 |
---|---|
|
デフォルトの入力ファイルのエンコーディングを指定します。XMLファイルでエンコーディングが指定されていない場合、このエンコーディングは入力対象とみなされます。 |
|
出力/データのエンコーディングを指定します。DOMおよび |
|
ハッシュのレベルを指定します。
|
|
グローバル最適化(デフォルト)を設定します。 |
|
ローカル最適化を設定します。 |
|
使用方法のヘルプを表示します。 |
|
更新操作の無効にします。 |
例23-1は、XmlDiff
およびXmlPatch
の使用による更新を説明するxml
文書のサンプルです。いくつかの変更が続きます。
次のアクションが結果的に発生すること以外は、例23-1とほぼ同じ内容の別のファイルbook2.xml
があるとします。
「The Eleventh Commandment」を削除(delete-node
操作の1つ)。
「C++ Primer」の国コードを米国から英国に変更(update-node
操作の1つ)。
「Emperor's New Mind」に説明を追加(append-node
操作の1つ)。
「Evening News」に版を追加(insert-node-before
操作の1つ)。
「Evening News」の価格を更新(update-node
操作の1つ)。
例23-1 book1.xml
<?xml version="1.0"?> <booklist xmlns="http://booklist.oracle.com"> <book> <title>Twelve Red Herrings</title> <author>Jeffrey Archer</author> <publisher>Harper Collins</publisher> <price>7.99</price> </book> <book> <title language="English">The Eleventh Commandment</title> <author>Jeffrey Archer</author> <publisher>McGraw Hill</publisher> <price>3.99</price> </book> <book> <title language="English" country="USA">C++ Primer</title> <author>Lippmann</author> <publisher>Harper Collins</publisher> <price>4.99</price> </book> <book> <title>Emperor's New Mind</title> <author>Roger Penrose</author> <publisher>Oxford Publishing Company</publisher> <price>15.9</price> </book> <book> <title>Evening News</title> <author>Arthur Hailey</author> <publisher>MacMillan Publishers</publisher> <price>9.99</price> </book> </booklist>
この項では、前の項で説明された2つのXMLファイルの比較により生成されたXdiff
インスタンス・ドキュメントを示します。次の項では、XML処理命令およびこの文書での操作について説明します。
次のようにしてXmlDiff
を起動します。
> xmldiff book1.xml book2.xml
この表では、引数およびフラグに関するサンプル・アプリケーションについても見ることができます。
例23-2 Xdiffインスタンス・ドキュメントのサンプル
<?xml version="1.0" encoding="UTF-8"?> <xd:xdiff xsi:schemaLocation="http://xmlns.oracle.com/xdb/xdiff.xsd xmlns:xd="http://xmlns.oracle.com/xdb/xdiff.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:oraxdfns_0="http://booklist.oracle.com"> <?oracle-xmldiff operations-in-docorder="true" output-model="snapshot" diff-algorithm="global"?> <xd:delete-node xd:node-type="element" xd:xpath="/oraxdfns_0 :booklist[1]/oraxdfns_0:book[2]"/> <xd:update-node xd:node-type="attribute" xd:parent-xpath="/oraxdfns_0:booklist[1]/oraxdfns_0:book[3]/oraxdfns_0 :title[1]" xd:attr-local="country"> <xd:content>US</xd:content> </xd:update-node> <xd:append-node xd:node-type="element" xd:parent-xpath="/oraxdfns_0 :booklist[1]/oraxdfns_0:book[4]"> <xd:content> <oraxdfns_0:description> This is a classic </oraxdfns_0:description> </xd:content> </xd:append-node> <xd:insert-node-before xd:node-type="element" xd:xpath="/oraxdfns_0 :booklist[1]/oraxdfns_0:book[5]/oraxdfns_0:author[1]"> <xd:content> <oraxdfns_0:edition>Hardcover</oraxdfns_0:edition> </xd:content> </xd:insert-node-before> <xd:update-node xd:node-type="text" xd:xpath="/oraxdfns_0 :booklist[1]/oraxdfns_0:book[5]/oraxdfns_0:price[1]/text()[1]"> <xd:content>12.99</xd:content> </xd:update-node> </xd:xdiff>
Xdiff
インスタンス・ドキュメントは、XML処理命令(前の項の太字表示箇所)を使用して、差分検出処理の特定の側面を表示します。「Xdiffスキーマ」を参照してください。命令および関連オプションは次のとおりです。
operations-in-docorder
: オプションはtrue
またはfalse
です。
true
: Xdiff
インスタンス・ドキュメントは、文書内と同じ順で最初の文書からノードを参照します。
false
: Xdiff
インスタンス・ドキュメントは、文書内と同じ順で最初の文書からノードを参照しません。
グローバル最適化の出力はoperations-in-docorder
要件に適合しますが、ローカル最適化では適合しません。
output-model
: オプションは次のとおりです。
snapshot
: Xmldiff
はスナップショット・モデルで出力を生成し、UNIXのdiffモデルに従います。入力文書にどの操作も適用しない場合は、XPath
が使用されます。これはデフォルトです。operations-in-docorder
がtrue
に設定され、XPath
が単純である場合のみ、XmlPatch
はこのモデルの処理を実行できます。単純なXPath
は子の軸をワイルド・カードなしで要求し、述語の位置を/root[1]/child[2]/text()[2]
のように使用する必要があります。
current
: 各操作は、前の操作までのすべての操作が入力文書に適用されているようにXPath
を使用します。XmlDiff
が差分を現在のモデルで生成しない場合でも、XmlPatch
は手作業で作成したdiff
文書を現在のモデルで処理できます。
diff-algorithm
: このオプションはどの最適化が差分を生成するかを示します。
グローバル最適化
ローカル最適化
関連項目:
XmlDiff
は、Xdiff
インスタンス・ドキュメントによって明示される操作を使用して差分を検出します。
表23-2 Xdiff操作の属性
属性 | 説明 |
---|---|
|
オペランド・ノードの親ノードの |
|
オペランド・ノードのタイプを指定します。 |
|
追加または挿入される新規のサブツリーまたは値を指定する子要素です。 |
Xdiff
インスタンス・ドキュメントに表示されるXdiff
操作は、次のとおりです。
append-node
append-node
要素により、指定されたタイプのノードが指定された親の最後の子へ追加されるように指定されます。
insert-node-before
insert-node-before
要素により、指定されたタイプのノードが指定された参照ノードの前に挿入されるように指定されます。
delete-node
delete-node
要素により、削除されるノードにそのすべての子が含まれるように指定されます。この要素を使用して、要素やコメントなどを削除できます。
update-node
update-node
は、指定されたXPath
式を含むノードに関連付けられた値が指定された新規の値に更新されるように指定します。内容は、テキスト・ノードに関する値です。属性の値は、属性ノードに関する値です。
テキスト・ノードの更新
update node操作の生成はユーザーによってオフにできます。
属性の値は、属性ノードに関する値です。
update-node
は、グローバル最適化によってのみテキスト・ノードに対して生成されます。
要素の更新
XmlDiff
は、要素ノードに対して更新操作を生成しません。
Xdiff
インスタンス・ドキュメントを手動で変更して、XmlPatch
を操作する更新操作を作成できます。または、Xdiff
インスタンス・ドキュメントを完全に手書きで入力することも可能です。更新操作に関わる要素のすべての子は削除されます。内容ノードで指定された新規のサブツリーは、すべてインポートされます。
XmlDiff
の出力であるXdiff
インスタンス・ドキュメントはXML形式で、次の項に示されるXdiff
スキーマに準拠しています。
出力文書には、2つの入力文書間の差分を示す一連の操作が含まれます。最初の文書から差分を適用する場合は、2番目の文書を取得します。
例23-3に、Xdiff
インスタンス・ドキュメント(出力)が従うXdiff
スキーマを示します。
例23-3 Xdiffスキーマ: xdiff.xsd
<schema targetNamespace="http://xmlns.oracle.com/xdb/xdiff.xsd" xmlns="http://www.w3.org/2001/XMLSchema" xmlns:xd="http://xmlns.oracle.com/xdb/xdiff.xsd" version="1.0" elementFormDefault="qualified" attributeFormDefault="qualified"> <annotation> <documentation xml:lang="en"> Defines the structure of XML documents that capture the difference between two XML documents. Changes that are not supported by Oracle XmlDiff may not be expressible in this schema. 'oracle-xmldiff' PI in Xdiff document: We use 'oracle-xmldiff' PI to describe certain aspects of the diff. The PI denotes values for 'operations-in-docorder' and 'output-model'. The output of XmlDiff has the PI always. If the user hand-codes a diff doc then it must also have the PI in it as the first child of top level xdiff element, to be able to call XmlPatch. operations-in-docorder: Can be either 'true' or 'false'. If true, the operations in the diff document refer to the elements of the input doc in the same order as document order. Output of global algorithm meets this requirement while local does not. output-model: output models for representing the diff. Can be either 'Snapshot' or 'Current'. Snapshot model: Each operation uses Xpaths as if no operations have been applied to the input document. (like UNIX diff) This is the model used in the output of XmlDiff. XmlPatch works with this (and the current model too). For XmlPatch to handle this model, "operations-in-docorder" must be true and the Xpaths must be simple. (see XmlDif C API documentation). Current model: Each operation uses Xpaths as if all operations till the previous one have been applied to the input document. Works with XmlPatch even if the 'operations-in-docorder' criterion is not met and the xpaths are not simple. <!-- Example: <?oracle-xmldiff operations-in-docorder="true" output-model= "snapshot" diff-algorithm="global"?> --> </documentation> </annotation> <!-- Enumerate the supported node types --> <simpleType name="xdiff-nodetype"> <restriction base="string"> <enumeration value="element"/> <enumeration value="attribute"/> <enumeration value="text"/> <enumeration value="cdata"/> <enumeration value="entity-reference"/> <enumeration value="entity"/> <enumeration value="processing-instruction"/> <enumeration value="notation"/> <enumeration value="comment"/> </restriction> </simpleType> <element name="xdiff"> <complexType> <choice minOccurs="0" maxOccurs="unbounded"> <element name="append-node"> <complexType> <sequence> <element name="content" type="anyType"/> </sequence> <attribute name="node-type" type="xd:xdiff-nodetype"/> <attribute name="xpath" type="string"/> <attribute name="parent-xpath" type="string"/> <attribute name="attr-local" type="string"/> <attribute name="attr-nsuri" type="string"/> </complexType> </element> <element name="insert-node-before"> <complexType> <sequence> <element name="content" type="anyType"/> </sequence> <attribute name="xpath" type="string"/> <attribute name="node-type" type="xd:xdiff-nodetype"/> </complexType> </element> <element name="delete-node"> <complexType> <attribute name="node-type" type="xd:xdiff-nodetype"/> <attribute name="xpath" type="string"/> <attribute name="parent-xpath" type="string"/> <attribute name="attr-local" type="string"/> <attribute name="attr-nsuri" type="string"/> </complexType> </element> <element name="update-node"> <complexType> <sequence> <element name="content" type="anyType"/> </sequence> <attribute name="node-type" type="xd:xdiff-nodetype"/> <attribute name="parent-xpath" type="string"/> <attribute name="xpath" type="string"/> <attribute name="attr-local" type="string"/> <attribute name="attr-nsuri" type="string"/> </complexType> </element> <element name="rename-node"> <complexType> <sequence> <element name="content" type="anyType"/> </sequence> <attribute name="xpath" type="string"/> <attribute name="node-type" type="xd:xdiff-nodetype"/> </complexType> </element> </choice> <attribute name="xdiff-version" type="string"/> </complexType> </element> </schema>
アプリケーションでは、XmlDiff
は、入力文書のソース・タイプおよび場所を引数とします。ソース・タイプは、URL、ファイル、orastream
およびstream
コンテキスト・ポインタ、バッファ、buffer_length
ポインタ、またはDOM文書要素(docelement
)へのポインタです。
XmlDiff
は、Xdiff
インスタンス・ドキュメントに関するDOMの文書ノードを戻します。
比較の実行前にDOMとして2つの文書が提供されていない場合、XmlDiffは2つの文書に対してDOMを構築します。
関連項目:
XmlDiff
の動作を制御するフラグのC APIの詳細は、『Oracle Database XML C APIリファレンス』を参照してください。
例23-4 XMLDiffアプリケーション
# include <xmldf.h> ... xmlctx *xctx; xmldocnode *doc1, *doc2, *doc3; uword hash_level; oratext *s, *inp1 = "book1.xml", *inp2="book2.xml"; xmlerr err; ub4 flags; flags = 0; /* defaults : global algorithm */ hash_level = 0; /* no hashing */ /* create XML meta context */ if (!(xctx = XmlCreate(&err, (oratext *) "XmlDiff", NULL))) { printf("Failed to create XML context, error %u\n", (unsigned) err); err_exit("Exiting"); } /* Load the two input files */ if (!(doc1 = XmlLoadDom(xctx, &err, "file", inp1, "discard_whitespace", TRUE, NULL))) { printf("Parsing first file failed, error %u\n", (unsigned)err); err_exit((oratext *)"Exiting."); } if (!(doc2 = XmlLoadDom(xctx, &err, "file", inp2, "discard_whitespace", TRUE, NULL))) { printf("Parsing second file failed, error %u\n", (unsigned)err); err_exit((oratext *)"Exiting."); } /* run XmlDiff on the DOM trees. */ doc3 = XmlDiff(xctx, &err, flags, XMLDF_SRCT_DOM, doc1, NULL, XMLDF_SRCT_DOM, doc2, NULL,hash_level, NULL); if(!doc3) printf("XmlDiff Failed, error %u\n", (unsigned)err); else { if(err != XMLERR_OK) printf("XmlDiff returned error %u\n", (unsigned)err); /* Now we have the DOM tree in doc3 which represent the Diff */ ... } XmlFreeDocument(xctx, doc1); XmlFreeDocument(xctx, doc2); XmlFreeDocument(xctx, doc3); XmlDestroy(xctx);
カスタマイズ済出力ビルダーは、アプリケーションに適したあらゆる形式の差分を格納します。XmlDiff
によって生成され、Xdiff
スキーマに準拠したデフォルトのXdiff
インスタンス・ドキュメントを使用するかわりに、ユーザー自身のカスタマイズ済出力ビルダーを作成できます。
カスタマイズ済出力ビルダーを作成するには、XmlDiff
が差分を決定した後にコールされるコールバックを提供する必要があります。差分はxmdlfop
の配列としてコールバックに渡されます。差分が生成されるたびに、コールバックが呼び出される場合があります。
XPath
生成に必要な内部状態の保守が不要なため、カスタマイズ済出力ビルダーを使用する方が、デフォルトを使用するパフォーマンスが向上する場合があります。
デフォルトでは、XmlDiff
はXdiff
スキーマに準拠するXMLでの差分を検出します。必要な場合は、出力ビルダーをプラグインしてください。差分は配列xmldfop
として表示されます。出力ビルダーのコールバック関数を書き込む必要があります。関数署名は次のとおりです。
xmlerr(*xdfobcb)(void *uctx, xmldfop *escript, ub4 escript_siz);
uctx
は、ユーザー指定のコンテキストです。
escript
は、サイズescript_siz
の配列です。
diff[escript_siz]
mctx
は、メモリー・コンテキストです。
プロパティを介してこのメモリー・コンテキストをXmlDiff()
に提供します。このメモリー・コンテキストを使用して、escript
に割り当てます。後でescript
を解放する必要があります。
XmlDiff()
の起動が戻る前でも発生する差分が検出されたら、出力ビルダーのコールバックを起動します。出力ビルダーのコールバックは、複数回コールされます。
例23-5 カスタマイズ済XMLDiff出力
/* Sample useage: */ ... #include <orastruc.h> / * for 'oraprop' * / ... static oraprop diff_props[] = { ORAPROP(XMLDF_PROPN_CUSTOM_OB, XMLDF_PROPI_CUSTOM_OB, POINTER), ORAPROP(XMLDF_PROPN_CUSTOM_OBMCX, XMLDF_PROPI_CUSTOM_OBMCX, POINTER), ORAPROP(XMLDF_PROPN_CUSTOM_OBUCX, XMLDF_PROPI_CUSTOM_OBUCX, POINTER), { NULL } }; ... oramemctx *mymemctx; ... xmlerr myob(void *uctx, xmldfop *escript, ub4 escript_siz) { /* process diff which is available in escript * / /* free escript - the caller has to do this * / OraMemFree(mymemctx, escript); } main() { ... myctxt *myctx; diff_props[0].value_oraprop.p_oraprop_v = myob; diff_props[1].value_oraprop.p_oraprop_v = mymemctx; diff_props[2].value_oraprop.p_oraprop_v = myctx; XmlDiff(xctx, &err, 0, doc1, NULL, 0, doc2, NULL, 0, diff_props); ...
XmlPatch
は、XmlDiff
によって生成されたものまたは別のメカニズムにより作成されたもののいずれかのXdiff
インスタンス・ドキュメントを受け取り、Xdiff
インスタンス・ドキュメント内の指示に従って、指定された他のXML文書を修正します。
表23-3に、XmlPatch
コマンドライン・オプションを示します。
表23-3 XmlPatch for Cコマンドライン・オプション
オプション | 説明 |
---|---|
|
デフォルトの入力ファイルのエンコーディングを指定します。XMLファイルでエンコーディングが指定されていない場合、このエンコーディングは入力対象とみなされます。 |
|
出力/データのエンコーディングを指定します。DOMおよびパッチ文書はこのエンコーディングで作成されます。デフォルトはUTF-8です。 |
|
ファイル名をURLとして解析します。 |
|
使用方法のヘルプを表示します。 |
XmlPatch
は、入力文書およびdiff
文書のソース・タイプおよび場所を引数とします。ソース・タイプは、URL、ファイル、orastream
およびstream
コンテキスト・ポインタ、バッファ、buffer_length
ポインタ、またはDOM文書要素(docelement
)へのポインタです。
関連項目:
XmlPatch
の動作を制御するフラグのC APIの詳細は、『Oracle Database XML C APIリファレンス』を参照してください。
Xdiff
スキーマによって設定されたモードはXmlPatch
の作業に影響します。
output-model
がSnapshot
の場合、XmlPatch
は、operations-in-docorder
がTRUE
の場合のみ作業します。
output-model
がCurrent
の場合、operations-in-docorder
をTRUE
に設定する必要はありません。
例23-6 XmlPatchに関するサンプル・アプリケーション
... #include <xmldf.h> ... xmlctx *xctx; xmldocnode *doc1, *doc2; oratext *s; oratext *inp1 = "book1.xml"; /* input document */ oratext *inp2 = "diff.xml", /* diff document */ xmlerr err; /* create XML meta context */ if (!(xctx = XmlCreate(&err, (oratext *) "XmlPatch", NULL))) { printf("Failed to create XML context, error %u\n", (unsigned) err); err_exit("Exiting"); } /* Load the two input files */ if (!(doc1 = XmlLoadDom(xctx, &err, "file", inp1, "discard_whitespace", TRUE, NULL))) { printf("Parsing first file failed, error %u\n", (unsigned)err); err_exit((oratext *)"Exiting."); } if (!(doc2 = XmlLoadDom(xctx, &err, "file", inp2, "discard_whitespace", TRUE, NULL))) { printf("Parsing second file failed, error %u\n", (unsigned)err); err_exit((oratext *)"Exiting."); } /* call XmlPatch */ if(!XmlPatch(xctx, &err, 0, XMLDF_SRCT_DOM, doc1, NULL, XMLDF_SRCT_DOM, doc2, NULL, NULL)); printf("XmlPatch Failed, error %u\n", (unsigned)err); else { if(err != XMLERR_OK) printf("XmlPatch returned error %u\n", (unsigned)err); /* Now we have the patched document in doc1 */ ... } XmlFreeDocument(xctx, doc1); XmlFreeDocument(xctx, doc2); XmlDestroy(xctx);
XDKにより、XMLツリーまたはサブツリーに対するハッシュ値を計算するXmlHash
が提供されます。2つのサブツリーのハッシュ値が同一の場合、同じXMLである可能性が非常に高いです。たとえば、XMLツリーがすでにデータベース内にあるかどうかを見る場合、XmlHash
を使用してすばやく比較できます。
必要な場合、一致がある可能性がある一致に対してXmlDiff
を再度実行できます。新規文書のハッシュ値を計算して、データベースに問合せを実行できます。
例23-7に、XmlHash
を使用するサンプル・プログラムを示します。
例23-7 XmlHashプログラム
sword main(sword argc, char *argv[]) { xmlctx *xctx; xmldfsrct srct; oratext *data_encoding, *input_encoding, *s, *inp1; ub1 flags; xmlerr err; ub4 num_args; xmlhasht digest; flags = 0; /* defaults */ srct = XMLDF_SRCT_FILE; inp1 = "somexml.xml"; xctx = XmlCreate(&err, (oratext *) "XmlHash", NULL); if (!xctx) { /* handle error with creating xml context and exit */ ... } /* run XmlHash */ err = XmlHash(xctx, &digest, 0, srct, inp1, NULL, NULL); if(err) printf("XmlHash returned error:%d \n", err); else txdfha_pd(digest); XmlDestroy(xctx); return (sword )err; } /* print bytes in xml hash */ static void txdfha_pd(xmlhasht digest) { ub4 i; for(i = 0; i < digest.l_xmlhasht; i++) printf("%x ", digest.d_xmlhasht[i]); printf("\n"); }