xquery version "0.9-ml"
(:
 : SharePoint Connector - Metadata API
 :
 : Copyright (c) 2002-2009 Mark Logic Corporation. All Rights Reserved.
 :)module "http://marklogic.com/sharepoint/metadata"

declare namespace mlsp = "http://marklogic.com/sharepoint"
declare namespace xh="http://www.w3.org/1999/xhtml"

default function namespace = "http://www.w3.org/2003/05/xpath-functions"

declare namespace mlsd = "http://marklogic.com/sharepoint/metadata"

(:
 : Helper functions
 :)

define function mlsd:add-trailing-slash(
  $path as xs:string
) as xs:string
{
  if (ends-with($path, "/")) then
    $path
  else
    concat($path, "/")
}

(: Create document URI from the metadata :)
define function mlsd:doc-path(
  $meta as element()*        (: SharePoint document properties :)
) as xs:string
{
  let $p := <p>{ $meta }</p>
  return
    if (string($p/mlsp:parentFolder) ne "") then
      concat(
        string($p/mlsp:webUrl), "/",
        string($p/mlsp:parentFolder), "/",
        string($p/mlsp:fileName)
        )
    else
      concat(
        string($p/mlsp:webUrl), "/",
        string($p/mlsp:fileName)
        )
}

(: Get document properties :)
define function mlsd:get-doc-props(
  $uri as xs:string*
) as element()*
{
  if (exists($uri)) then
    (: Get all properties :)
    xdmp:document-properties($uri)//mlsp:*
  else
    ()
}

(: Get directory properties :)
define function mlsd:get-dir-props(
  $uri as xs:string*
) as element()*
{
  if (exists($uri)) then
    (: Get all properties :)
    xdmp:document-properties(mlsd:add-trailing-slash($uri))//mlsp:*
  else
    ()
}

define function mlsd:get-props(
  $uri as xs:string*
) as element()*
{
  if (exists($uri)) then
    let $props := mlsd:get-doc-props($uri)
    let $dprops := mlsd:get-dir-props($uri)
    return
      if (exists($props)) then
        $props
      else
        $dprops
  else
    ()
}

(: Property accessor functions - there must be a better way :)

define function mlsd:props-get-type(
  $props as element()*
) as element()?
{
  <p>{ $props }</p>/mlsp:type
}

define function mlsd:props-get-uri(
  $props as element()*
) as element()?
{
  <p>{ $props }</p>/mlsp:uri
}

define function mlsd:props-get-webUrl(
  $props as element()*
) as element()?
{
  <p>{ $props }</p>/mlsp:webUrl
}

define function mlsd:props-get-parentFolder(
  $props as element()*
) as element()?
{
  <p>{ $props }</p>/mlsp:parentFolder
}

define function mlsd:props-get-fileName(
  $props as element()*
) as element()?
{
  <p>{ $props }</p>/mlsp:fileName
}

define function mlsd:props-get-length(
  $props as element()*
) as element()?
{
  <p>{ $props }</p>/mlsp:length
}

define function mlsd:props-get-timeCreated(
  $props as element()*
) as element()?
{
  <p>{ $props }</p>/mlsp:timeCreated
}

define function mlsd:props-get-timeLastModified(
  $props as element()*
) as element()?
{
  <p>{ $props }</p>/mlsp:timeLastModified
}

define function mlsd:props-get-mode(
  $props as element()*
) as element()?
{
  <p>{ $props }</p>/mlsp:mode
}

define function mlsd:props-get-hash(
  $props as element()*
) as element()?
{
  <p>{ $props }</p>/mlsp:hash
}

define function mlsd:props-get-pubLevel(
  $props as element()*
) as element()?
{
  <p>{ $props }</p>/mlsp:pubLevel
}

define function mlsd:props-get-majorVersion(
  $props as element()*
) as element()?
{
  <p>{ $props }</p>/mlsp:majorVersion
}

define function mlsd:props-get-minorVersion(
  $props as element()*
) as element()?
{
  <p>{ $props }</p>/mlsp:minorVersion
}

define function mlsd:props-get-title(
  $props as element()*
) as element()?
{
  <p>{ $props }</p>/mlsp:title
}

(: Create metadata URI from the metadata :)
define function mlsd:metadata-to-nvp(
  $props as element()*         (: SharePoint document properties :)
) as xs:string*
{
  if (string(mlsd:props-get-type($props)) eq "doc") then
  (
    concat("type=", string(mlsd:props-get-type($props))),
    concat("uri=",  string(mlsd:props-get-uri($props))),
    concat("webUrl=", string(mlsd:props-get-webUrl($props))),
    concat("parentFolder=", string(mlsd:props-get-parentFolder($props))),
    concat("name=", string(mlsd:props-get-fileName($props))),
    
    concat("length=", string(mlsd:props-get-length($props))),
    concat("timeCreated=", string(mlsd:props-get-timeCreated($props))),
    concat("timeLastModified=", string(mlsd:props-get-timeLastModified($props))),
    
    concat("pubLevel=", string(mlsd:props-get-pubLevel($props))),
    concat("majorVersion=", string(mlsd:props-get-majorVersion($props))),
    concat("minorVersion=", string(mlsd:props-get-minorVersion($props))),
    concat("title=", string(mlsd:props-get-title($props))),
    
    concat("mode=", string(mlsd:props-get-mode($props))),
    concat("hash=", string(mlsd:props-get-hash($props)))
  )
  else
  (
    concat("type=", string(mlsd:props-get-type($props))),
    concat("uri=",  string(mlsd:props-get-uri($props))),
    concat("webUrl=", string(mlsd:props-get-webUrl($props))),
    concat("parentFolder=", string(mlsd:props-get-parentFolder($props))),
    concat("name=", string(mlsd:props-get-fileName($props))),
    
    concat("mode=", string(mlsd:props-get-mode($props)))
  )
}


(: Return all the distinct site URLs from the metadata :)
define function mlsd:get-sites(
) as xs:string*
{
  distinct-values(xdmp:document-properties()/property::mlsp:webUrl)
}

(: Return site from the path URI :)
define function mlsd:get-site-from-path(
  $path as xs:string
) as xs:string*
{
  (: Pick the longest matching site name :)
  let $list :=
    for $s in mlsd:get-sites()[starts-with($path, .)]
    order by string-length($s) descending
    return
      $s
  return
    $list[1]
}

(: Return parent folder from the path URI :)
define function mlsd:get-parentFolder-from-path(
  $path as xs:string
) as xs:string*
{
  let $site := mlsd:get-site-from-path($path)
  let $site-length := string-length($site)
  return
    if ($path eq $site) then
      ""
    else
      replace(substring($path, $site-length + 1), "/(.*)/([^/]*)$", "$1")
}

(: Return file name from the path URI :)
define function mlsd:get-fileName-from-path(
  $path as xs:string
) as xs:string*
{
  let $site := mlsd:get-site-from-path($path)
  let $site-length := string-length($site)
  return
    if ($path eq $site) then
      ""
    else
      replace(substring($path, $site-length + 1), "(.*)/([^/]*)$", "$2")
}

(: Return the closest folder containing the document described by the metadata :)
define function mlsd:get-controlling-folder-from-path(
  $path as xs:string      (: URI of document :)
) as xs:string*
{
  let $list := xdmp:document-properties()
      [./property::mlsp:type eq "folder" and starts-with($path, ./property::mlsp:uri)]
      /property::mlsp:uri
  return
    if (count($list) eq 0) then
      ()
    else
      (: Choose the longest folder path name :)
      let $olist :=
          for $d in $list
          let $u := string($d)
          order by string-length($u) descending
          return
            $u
      return
        $olist[1]
}

define function mlsd:get-controlling-folder(
  $meta                   (: Document metadata :)
) as xs:string*
{
  let $path := mlsd:doc-path($meta)
  return
    mlsd:get-controlling-folder-from-path($path)
}

(: Return SharePoint metadata renamed for use in document properties :)
define function mlsd:rename-sharepoint-props(
  $oldUri as xs:string,   (: URI of document to renamed (directories should have a trailing slash) :)
  $newUri as xs:string    (: URI of renamed document (should have no trailing slashes) :)        
) as element()*
{
  let $oldMeta := xdmp:document-properties($oldUri)//mlsp:*
  let $p1 := <props>{ $oldMeta }</props>
  let $p2 :=
    <props>
      { $p1/(* except (mlsp:uri, mlsp:parentFolder, mlsp:fileName)) } 
      <mlsp:uri>{ $newUri }</mlsp:uri>
      <mlsp:parentFolder>{ mlsd:get-parentFolder-from-path($newUri) }</mlsp:parentFolder>
      <mlsp:fileName>{ mlsd:get-fileName-from-path($newUri) }</mlsp:fileName>
    </props>
  return
    $p2/*
}

(: Rename a document :)
define function mlsd:rename(
  $oldUri as xs:string,
  $newUri as xs:string
) as empty()
{
  if (exists(doc($oldUri))) then
  (
    xdmp:document-insert($newUri, doc($oldUri)),
    xdmp:document-delete($oldUri),
    
    ()
  )
  else
    ()
}

(: Copy a directory and SharePoint properties :)
define function mlsd:directory-copy(
  $oldUri as xs:string,
  $newUri as xs:string
) as empty()
{
  (:
   : foreach uri (get uri list prefaced by oldUri and sort by decreasing string length)
   :   if uri is a document
   :     rename uri
   :   else
   :     create new directory
   :     delete old directory
   :)
  let $oldlength := string-length($oldUri)
  let $oldnd := mlsd:add-trailing-slash($oldUri) (: Used suffixed name to insure we don't get other dirs with the same prefix :)
  let $list := 
      for $z in xdmp:document-properties()[.//mlsp:uri and starts-with(.//mlsp:uri, $oldnd)]
      order by string-length($z//mlsp:uri) descending
      return
        $z
  for $z in $list
  let $props := $z/prop:properties/mlsp:*
  let $n := mlsd:doc-path($props)
  let $newn := concat($newUri, substring($n, $oldlength + 1))
  let $nd := mlsd:add-trailing-slash($n)
  let $newnd := mlsd:add-trailing-slash($newn)
  return
    if (mlsd:props-get-type($props) eq "doc") then
      let $newmeta := mlsd:rename-sharepoint-props($n, $newn)
      return
      (
        (: concat("doc ", $n, " to ", $newn) :)
        xdmp:document-insert($newn, doc($n)),
        xdmp:document-set-properties($newn, $newmeta)
      )
    else
      let $newmeta := mlsd:rename-sharepoint-props($nd, $newn)
      let $newndprops := xdmp:document-properties($newnd)//mlsp:* 
      return
        (: Directory props may already exist because of update events :)
        if (not(exists($newndprops))) then
        (
          (: concat("folder ", $nd, " to ", $newnd) :)
          xdmp:directory-create($newnd),
          xdmp:document-set-properties($newnd, $newmeta)
        )
        else
        (
          xdmp:document-set-properties($newnd, $newmeta)
        )

}


(:
 : Item semantics (the QNames used here are defined in the C# function MLSP.GetMetadata())
 :)

(: Is the first metadata node more recent than the second? :)
define function mlsd:is-more-recent(
  $props1 as element()*,
  $props2 as element()*
) as xs:boolean
{
  let $t1 := mlsd:props-get-timeLastModified($props1)
  let $t2 := mlsd:props-get-timeLastModified($props2)
  return
    if (exists($t1) and exists($t2)
        and xs:unsignedLong($t1) > xs:unsignedLong($t2)) then
      true()
    else
      false()
}

define function mlsd:hash-changed(
  $props1 as element()*,
  $props2 as element()*
) as xs:boolean
{
  let $h1 := string(mlsd:props-get-hash($props1))
  let $h2 := string(mlsd:props-get-hash($props2))
  return
    if (not(exists($h1)) and not(exists($h2))) then
      true() (: If no hash then assume the doc has changed :)
    else if (exists($h1) and exists($h2)
        and compare($h1, $h2) ne 0) then
      true()
    else
      false()
}
