跳转到内容

XQuery/XML 模式到实例

来自 Wikibooks,开放世界中的开放书籍

为了从 XML 模式文件 (XSD) 生成一个示例 XML 实例,例如,如果您想动态生成空白的 XForms 实例以从模式创建新文档,这将非常有用。


[注意:另请参阅文章 XQuery/XQuery 和 XML 模式,它具有相同的目标。如果作者想联系我,我正在尝试使此代码工作,以便与我开发的代码进行比较。ChrisWallace (讨论) 15:09, 2009年5月13日 (UTC)ChrisWallace]

创建一个 xquery 函数,该函数读取 XML 模式文件 (.xsd) 的 URI 以及一组显示参数,并生成一个示例 XML 实例。这些参数是

  1. $schemaURI = .xsd 文件的位置(例如 db/cms/schemas/MySchema.xsd)
  2. $rootElementName = 您希望生成的示例 XML 文件的根元素(即,不必是整个模式的根)
  3. $maxOccurances = 对于 maxOccurs 属性大于 1 的元素,该元素在示例实例中应重复多少次?
  4. $optionalElements = 是否应包含可选元素(即 minOccurs="0")?'true' 或 'false'
  5. $optionalAttributes = 是否应包含可选属性(即 use="optional")?'true' 或 'false'
  6. $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 调用它们。这使得能够递归地收集子元素和属性,而无需调用外部函数。这两个查询也可以轻松地声明为外部函数。

XSD 到实例函数

[编辑 | 编辑源代码]
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)
}
};
华夏公益教科书