跳轉到內容

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)
}
};
華夏公益教科書