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)
}
};