XQuery/XML 模式到例項
外觀
< XQuery
為了從 XML 模式檔案 (XSD) 生成一個示例 XML 例項,例如,如果您想動態生成空白的 XForms 例項以從模式建立新文件,這將非常有用。
[注意:另請參閱文章 XQuery/XQuery 和 XML 模式,它具有相同的目標。如果作者想聯絡我,我正在嘗試使此程式碼工作,以便與我開發的程式碼進行比較。ChrisWallace (討論) 15:09, 2009年5月13日 (UTC)ChrisWallace]
建立一個 xquery 函式,該函式讀取 XML 模式檔案 (.xsd) 的 URI 以及一組顯示引數,並生成一個示例 XML 例項。這些引數是
- $schemaURI = .xsd 檔案的位置(例如 db/cms/schemas/MySchema.xsd)
- $rootElementName = 您希望生成的示例 XML 檔案的根元素(即,不必是整個模式的根)
- $maxOccurances = 對於 maxOccurs 屬性大於 1 的元素,該元素在示例例項中應重複多少次?
- $optionalElements = 是否應包含可選元素(即 minOccurs="0")?'true' 或 'false'
- $optionalAttributes = 是否應包含可選屬性(即 use="optional")?'true' 或 'false'
- $choiceStrategy = 在元素或元素組之間進行選擇時,示例是否應包含來自選擇的隨機選擇,或者僅使用第一個選擇?'random' 或 'first'
使用以下內容呼叫函式
xquery version "1.0";
(: Query which calls the function :)
import module namespace content ="/db/cms/modules/content" at "/db/cms/modules/content.xqm";
return
content:xsd-to-instance('/db/cms/content_types/schemas/Genericode.xsd','CodeList','1','true','true','random')
該函式目前無法動態設定示例例項中的名稱空間。非常感謝您提供任何幫助來使此功能正常工作。
該函式要求 xsd 檔案使用 xs 名稱空間字首(即 xs:element)。由於某種原因,嘗試在 xpath 語句中使用萬用字元字首不起作用(即 $xsdFile/*:schema/*:element)。另一種方法是確定字首,將其分配給變數,然後將其連線到所有 xpath 語句(例如 $xsdFile/concat($prefix,':schema/',$prefix,'element'),但這會產生一些非常難看的程式碼。另一種方法是使用另一個函式將 xsd 檔案字首重置為 xs。這確實可以正常工作,但會增加一些程式碼。歡迎提供任何更有效的替代建議。
該函式使用分配給變數 $subElementsQuery 和 $attributesQuery 的兩個內部查詢,然後使用 util:eval 呼叫它們。這使得能夠遞迴地收集子元素和屬性,而無需呼叫外部函式。這兩個查詢也可以輕鬆地宣告為外部函式。
xquery version "1.0";
(: Content Module :)
module namespace content ="/db/cms/modules/content";
declare namespace request="http://exist-db.org/xquery/request";
declare namespace util="http://exist-db.org/xquery/util";
(: Function :)
declare function content:xsd-to-instance($schemaURI,$rootElementName,$maxOccurances,$optionalElements,$optionalAttributes,$choiceStrategy)
{
(:
TO DO:
- Handle substitution groups
- Dynamically include namespaces
- Handle any xsd file prefix (e.g. xs:element or xsd:element)
:)
(: Get the main xsd file :)
let $xsdFile := doc($schemaURI)
(: Determine the namespace prefix for the xsd file (e.g. xs, xsd or none) :)
let $xsdFileNamespacePrefix := substring-before(name($xsdFile/*[1]),':')
(: get the root element based on the root element name given in the function parameters :)
let $rootElement := $xsdFile//xs:element[@name = $rootElementName]
(: Gather the namespace prefixes and namespaces included in the xsd file :)
let $namespaces := let $prefixes := in-scope-prefixes($xsdFile/xs:schema)
return
<Namespaces>
{for $prefix in $prefixes
return
<Namespace prefix="{$prefix}" URI="{namespace-uri-for-prefix($prefix,$xsdFile/xs:schema)}"/>}
</Namespaces>
(: Determine the namespace prefix and namespace for the root element :)
let $rootElementNamespace := $xsdFile/xs:schema/@targetNamespace
let $rootElementNamespacePrefix := $namespaces/Namespace[@URI = $rootElementNamespace]/@prefix
(: If the root element is a complex type, locate the complex type (not sure why the [1] predicate is required) :)
let $rootElementType := substring-after($rootElement[1]/@type,':')
let $namespacePrefix := substring-before($rootElement[1]/@type,':')
let $schemaFromPrefixQuery := string("
let $namespace := namespace-uri-for-prefix($namespacePrefix,$xsdFile/*[1])
let $schemaLocation := if($namespace = $xsdFile/xs:schema/@targetNamespace or $namespace = '')
then $schemaURI
else $xsdFile//xs:import[@namespace = $namespace]/@schemaLocation
let $schema := if($schemaLocation = $schemaURI)
then $xsdFile
else doc($schemaLocation)
return
$schema
")
let $rootElementTypeSchema := util:eval($schemaFromPrefixQuery)
let $complexType := if($rootElement/xs:complexType)
then $rootElement/xs:complexType
else if($namespacePrefix = 'xs' or $namespacePrefix = 'xsd')
then ()
else if($rootElementTypeSchema//xs:complexType[@name = $rootElementType])
then $rootElementTypeSchema//xs:complexType[@name = $rootElementType]
else()
(: Query to recursively drill down to find the appropriate elements.
If the complex type is a choice, include only the first sub-element.
If the complex type is a group, include the group sub-elements.
If the complex type is an extension, include the base sub-elements :)
let $subElementsQuery := string("
for $xsElement in $complexType/*
return
if(name($xsElement)='xs:all')
then let $complexType := $complexType/xs:all
return util:eval($subElementsQuery)
else if(name($xsElement)='xs:sequence')
then let $complexType := $complexType/xs:sequence
return util:eval($subElementsQuery)
else if(name($xsElement)='xs:choice')
then let $choice := if($choiceStrategy = 'random')
then let $choiceCount := count($xsElement/*)
return $choiceCount - util:random($choiceCount)
else 1
return
if(name($xsElement/*[$choice])='xs:element')
then let $subElementName := if($xsElement/*[$choice]/@name)
then data($xsElement/*[$choice]/@name)
else data(substring-after($xsElement/*[$choice]/@ref,':'))
let $namespace := namespace-uri-for-prefix($namespacePrefix,$xsdFile/*[1])
let $schemaLocation := if($namespace = $xsdFile/xs:schema/@targetNamespace or $namespace = '')
then $schemaURI
else $xsdFile//xs:import[@namespace = $namespace]/@schemaLocation
let $minOccurs := $xsElement/*[$choice]/@minOccurs
let $maxOccurs := $xsElement/*[$choice]/@maxOccurs
return
<SubElement>
<Name>{$subElementName}</Name>
<NamespacePrefix>{$namespacePrefix}</NamespacePrefix>
<Namespace>{$namespace}</Namespace>
<SchemaLocation>{$schemaLocation}</SchemaLocation>
<MinOccurs>{$minOccurs}</MinOccurs>
<MaxOccurs>{$maxOccurs}</MaxOccurs>
</SubElement>
else if(name($xsElement/*[$choice])='xs:group')
then let $groupName := substring-after($xsElement/*[$choice]/@ref,':')
let $namespacePrefix := substring-before($xsElement/*[$choice]/@ref,':')
let $groupSchema := util:eval($schemaFromPrefixQuery)
let $complexType := $groupSchema//xs:group[@name = $groupName]
return util:eval($subElementsQuery)
else let $complexType := $xsElement/*[$choice]
return util:eval($subElementsQuery)
else if(name($xsElement)='xs:group')
then let $groupName := substring-after($xsElement/@ref,':')
let $namespacePrefix := substring-before($xsElement/@ref,':')
let $groupSchema := util:eval($schemaFromPrefixQuery)
let $complexType := $groupSchema//xs:group[@name = $groupName]
return util:eval($subElementsQuery)
else if(name($xsElement)='xs:complexContent')
then let $complexType := $complexType/xs:complexContent
return util:eval($subElementsQuery)
else if(name($xsElement)='xs:extension')
then let $extension := let $complexType := $complexType/xs:extension
return util:eval($subElementsQuery)
let $base := let $baseName := substring-after($xsElement/@base,':')
let $namespacePrefix := substring-before($xsElement/@base,':')
let $baseSchema := util:eval($schemaFromPrefixQuery)
let $complexType := $baseSchema//xs:complexType[@name = $baseName]
return util:eval($subElementsQuery)
return $base union $extension
else if(name($xsElement)='xs:element')
then let $subElementName := if($xsElement/@name)
then data($xsElement/@name)
else data(substring-after($xsElement/@ref,':'))
let $namespace := namespace-uri-for-prefix($namespacePrefix,$xsdFile/*[1])
let $schemaLocation := if($namespace = $xsdFile/xs:schema/@targetNamespace or $namespace = '')
then $schemaURI
else $xsdFile//xs:import[@namespace = $namespace]/@schemaLocation
let $minOccurs := $xsElement/@minOccurs
let $maxOccurs := $xsElement/@maxOccurs
return
<SubElement>
<Name>{$subElementName}</Name>
<NamespacePrefix>{$namespacePrefix}</NamespacePrefix>
<Namespace>{$namespace}</Namespace>
<SchemaLocation>{$schemaLocation}</SchemaLocation>
<MinOccurs>{$minOccurs}</MinOccurs>
<MaxOccurs>{$maxOccurs}</MaxOccurs>
</SubElement>
else()
")
(: Employ the sub-elements query to gather the sub-elements :)
let $subElements := util:eval($subElementsQuery)
(: Query to recursively drill down to find the appropriate attributes :)
let $attributesQuery := string("
for $xsElement in $complexType/*
return
if(name($xsElement)='xs:attributeGroup')
then let $attributeGroupName := substring-after($xsElement/@ref,':')
let $namespacePrefix := substring-before($xsElement/@ref,':')
let $attributeGroupSchema := util:eval($schemaFromPrefixQuery)
let $complexType := $attributeGroupSchema//xs:attributeGroup[@name = $attributeGroupName]
return util:eval($attributesQuery)
else if(name($xsElement)='xs:complexContent')
then let $complexType := $complexType/xs:complexContent
return util:eval($attributesQuery)
else if(name($xsElement)='xs:extension')
then let $extension := let $complexType := $complexType/xs:extension
return util:eval($attributesQuery)
let $base := let $baseName := substring-after($xsElement/@base,':')
let $namespacePrefix := substring-before($xsElement/@base,':')
let $baseSchema := util:eval($schemaFromPrefixQuery)
let $complexType := $baseSchema//xs:complexType[@name = $baseName]
return util:eval($attributesQuery)
return $base union $extension
else if(name($xsElement)='xs:attribute')
then $xsElement
else()
")
(: Employ the attributes query to gather the attributes :)
let $attributes := util:eval($attributesQuery)
return
(: Create the root element :)
element{if($rootElementNamespacePrefix)
then concat($rootElementNamespacePrefix,':',$rootElementName)
else $rootElementName
}
{
(: for the time being, namespace attributes must be hard coded :)
namespace gc {'http://www.test.com'}
(: The following should dynamically insert namespace attributes with prefixes but does not work.
It would be great id someone could help figure this out.
for $namespace in $namespaces
return
namespace {$namespace/Namespace/@prefix} {$namespace/Namespace/@URI},
:)
,(: Comma is important, separates the namespaces section from the attribute section in the element constructor :)
(: Create the element's attributes if any :)
for $attribute in $attributes
let $attributeName := if($attribute/@name)
then data($attribute/@name)
else data($attribute/@ref)
return
(: Make sure there is an attribute before calling the attribute constructor :)
if($attributeName)
then if($attribute/@use = 'optional')
then if($optionalAttributes eq 'true')
then attribute{$attributeName}
(: Insert default attribute value if any :)
{if($attribute/@default) then data($attribute/@default) else if($attribute/@fixed) then data($attribute/@fixed) else ()}
else()
else if($attribute/@use = 'prohibited')
then ()
else attribute{$attributeName}
(: Insert default attribute value if any :)
{if($attribute/@default) then data($attribute/@default) else if($attribute/@fixed) then data($attribute/@fixed) else ()}
else()
,(: Comma separates the attribute section from the element content section in the element constructor :)
(: Insert default element value if any :)
if($rootElement/@default)
then data($rootElement/@default)
else if($rootElement/@fixed)
then data($rootElement/@fixed)
else
(: Recursively create any sub-elements :)
for $subElement in $subElements
let $subElementName := $subElement/Name
let $namespacePrefix := $subElement/NamespacePrefix
let $schemaURI := $subElement/SchemaLocation
(: Set the number of element occurances based on the minOccurances and maxOccurances values if any :)
let $occurances := if(xs:integer($subElement/@minOccurs) gt 0 and xs:integer($subElement/@minOccurs) gt xs:integer($maxOccurances))
then xs:integer($subElement/@minOccurs)
else if(xs:integer($subElement/@minOccurs) eq 0 and $optionalElements eq 'false')
then 0
else if($subElement/@maxOccurs eq 'unbounded')
then if($maxOccurances)
then xs:integer($maxOccurances)
else 2
else if(xs:integer($subElement/@maxOccurs) gt 1)
then if(xs:integer($maxOccurances) lt xs:integer($subElement/@maxOccurs))
then xs:integer($maxOccurances)
else xs:integer($subElement/@maxOccurs)
else 1
return
for $i in (1 to $occurances)
return
content:xsd-to-instance($schemaURI,$subElementName,$maxOccurances,$optionalElements,$optionalAttributes,$choiceStrategy)
}
};