swish-0.10.4.0/app/ 0000755 0000000 0000000 00000000000 14205001511 012022 5 ustar 00 0000000 0000000 swish-0.10.4.0/scripts/ 0000755 0000000 0000000 00000000000 13767226176 012765 5 ustar 00 0000000 0000000 swish-0.10.4.0/src/ 0000755 0000000 0000000 00000000000 14220136201 012033 5 ustar 00 0000000 0000000 swish-0.10.4.0/src/Data/ 0000755 0000000 0000000 00000000000 13543702315 012720 5 ustar 00 0000000 0000000 swish-0.10.4.0/src/Data/Interned/ 0000755 0000000 0000000 00000000000 14312330055 014461 5 ustar 00 0000000 0000000 swish-0.10.4.0/src/Data/Ord/ 0000755 0000000 0000000 00000000000 13543702315 013444 5 ustar 00 0000000 0000000 swish-0.10.4.0/src/Data/String/ 0000755 0000000 0000000 00000000000 13543702315 014166 5 ustar 00 0000000 0000000 swish-0.10.4.0/src/Network/ 0000755 0000000 0000000 00000000000 13543702315 013500 5 ustar 00 0000000 0000000 swish-0.10.4.0/src/Network/URI/ 0000755 0000000 0000000 00000000000 13543702315 014137 5 ustar 00 0000000 0000000 swish-0.10.4.0/src/Swish/ 0000755 0000000 0000000 00000000000 14312330055 013135 5 ustar 00 0000000 0000000 swish-0.10.4.0/src/Swish/RDF/ 0000755 0000000 0000000 00000000000 14220136201 013543 5 ustar 00 0000000 0000000 swish-0.10.4.0/src/Swish/RDF/BuiltIn/ 0000755 0000000 0000000 00000000000 14220136201 015111 5 ustar 00 0000000 0000000 swish-0.10.4.0/src/Swish/RDF/Datatype/ 0000755 0000000 0000000 00000000000 13543702315 015332 5 ustar 00 0000000 0000000 swish-0.10.4.0/src/Swish/RDF/Datatype/XSD/ 0000755 0000000 0000000 00000000000 14220136201 015754 5 ustar 00 0000000 0000000 swish-0.10.4.0/src/Swish/RDF/Formatter/ 0000755 0000000 0000000 00000000000 14312330055 015513 5 ustar 00 0000000 0000000 swish-0.10.4.0/src/Swish/RDF/Parser/ 0000755 0000000 0000000 00000000000 14312330055 015004 5 ustar 00 0000000 0000000 swish-0.10.4.0/src/Swish/RDF/Vocabulary/ 0000755 0000000 0000000 00000000000 14162356332 015670 5 ustar 00 0000000 0000000 swish-0.10.4.0/src/Swish/Utils/ 0000755 0000000 0000000 00000000000 13543702315 014244 5 ustar 00 0000000 0000000 swish-0.10.4.0/tests/ 0000755 0000000 0000000 00000000000 14205054002 012407 5 ustar 00 0000000 0000000 swish-0.10.4.0/src/Data/Interned/URI.hs 0000644 0000000 0000000 00000006523 14220136201 015455 0 ustar 00 0000000 0000000 {-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeFamilies #-}
#if (__GLASGOW_HASKELL__ >= 802)
{-# LANGUAGE DerivingStrategies #-}
#endif
--------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : URI
-- Copyright : (c) 2011, 2012, 2022 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : CPP, DerivingStrategies, FlexibleInstances, TypeFamilies
--
-- Support interning URIs.
--
--------------------------------------------------------------------------------
module Data.Interned.URI
( InternedURI
) where
import Data.String (IsString(..))
import Data.Hashable
import Data.Interned
import Data.Maybe (fromMaybe)
import Network.URI
-- Could look at adding UNPACK statements before the Int component
-- | An interned URI. The hashing is based on the
-- reversed URI (as a string).
data InternedURI = InternedURI !Int !URI
instance IsString InternedURI where
fromString = intern .
fromMaybe (error "Error: unable to create a URI.") .
parseURIReference
instance Eq InternedURI where
InternedURI a _ == InternedURI b _ = a == b
instance Ord InternedURI where
compare (InternedURI a _) (InternedURI b _) = compare a b
instance Show InternedURI where
showsPrec d (InternedURI _ b) = showsPrec d b
instance Interned InternedURI where
type Uninterned InternedURI = URI
data Description InternedURI = DU !URI
deriving
#if (__GLASGOW_HASKELL__ >= 802)
stock
#endif
(Eq)
describe = DU
identify = InternedURI
#if MIN_VERSION_intern(0,9,0)
#else
identity (InternedURI i _) = i
#endif
cache = iuCache
instance Uninternable InternedURI where
unintern (InternedURI _ b) = b
-- Rather than access the URI components, just use the reverse of the
-- string representation of the URI.
instance Hashable (Description InternedURI) where
#if MIN_VERSION_hashable(1,2,0)
#else
hash = hashWithSalt 5381 -- use the stringSalt value from Data.Hashable
#endif
hashWithSalt salt (DU u) = hashWithSalt salt ((reverse . show) u)
iuCache :: Cache InternedURI
iuCache = mkCache
{-# NOINLINE iuCache #-}
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012, 2022 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Data/Ord/Partial.hs 0000644 0000000 0000000 00000027335 13543702315 015406 0 ustar 00 0000000 0000000 --------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : Partial
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011, 2012 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : H98
--
-- This module provides methods to support operations on partially ordered
-- collections. The partial ordering relationship is represented by
-- 'Maybe' 'Ordering'.
--
-- Thanks to members of the haskell-cafe mailing list -
-- Robert (rvollmert-lists\@gmx.net) and
-- Tom Pledger (Tom.Pledger\@peace.com) -
-- who suggested key ideas on which some of the code in this module is based.
--
--------------------------------------------------------------------------------
-- at present the only user of this module is Swish.RDF.ClassRestrictionRule
module Data.Ord.Partial
( PartCompare
-- * Finding the range of a part-ordered list
, minima
, maxima
-- * Comparing part-ordered containers
, partCompareEq
, partComparePair
, partCompareListMaybe
, partCompareListSubset
)
where
import Data.List (foldl')
------------------------------------------------------------
-- Type of partial compare function
------------------------------------------------------------
-- | Partial comparison function.
type PartCompare a = a -> a -> Maybe Ordering
------------------------------------------------------------
-- Functions for minima and maxima of a part-ordered list
------------------------------------------------------------
-- |This function finds the maxima in a list of partially
-- ordered values, preserving the sequence of retained
-- values from the supplied list.
--
-- It returns all those values in the supplied list
-- for which there is no larger element in the list.
--
maxima :: PartCompare a -> [a] -> [a]
maxima cmp = foldl' add []
where
add [] e = [e]
add ms@(m:mr) e = case cmp m e of
Nothing -> m : add mr e
Just GT -> ms
Just EQ -> ms
Just LT -> add mr e
-- |This function finds the minima in a list of partially
-- ordered values, preserving the sequence of retained
-- values from the supplied list.
--
-- It returns all those values in the supplied list
-- for which there is no smaller element in the list.
--
minima :: PartCompare a -> [a] -> [a]
minima cmp = maxima (flip cmp)
------------------------------------------------------------
-- Partial ordering comparison functions
------------------------------------------------------------
-- |Partial ordering for Eq values
partCompareEq :: (Eq a) => PartCompare a
partCompareEq a1 a2 = if a1 == a2 then Just EQ else Nothing
-- |Part-ordering comparison on pairs of values,
-- where each has a part-ordering relationship
partComparePair ::
PartCompare a
-> PartCompare b
-> (a,b)
-> (a,b)
-> Maybe Ordering
partComparePair cmpa cmpb (a1,b1) (a2,b2) = case (cmpa a1 a2,cmpb b1 b2) of
(_,Nothing) -> Nothing
(jc1,Just EQ) -> jc1
(Nothing,_) -> Nothing
(Just EQ,jc2) -> jc2
(Just c1,Just c2) -> if c1 == c2 then Just c1 else Nothing
-- |Part-ordering comparison on lists of partially ordered values, where:
--
-- [@as==bs@] if members of as are all equal to corresponding members of bs
--
-- [@as<=bs@] if members of as are all less than or equal to corresponding
-- members of bs
--
-- [@as>=bs@] if members of as are all greater than or equal to corresponding
-- members of bs
--
-- [otherwise] as and bs are unrelated
--
-- The comparison is restricted to the common elements in the two lists.
--
partCompareListPartOrd :: PartCompare a -> [a] -> [a] -> Maybe Ordering
partCompareListPartOrd cmp a1s b1s = pcomp a1s b1s EQ
where
pcomp (a:as) (b:bs) ordp = case cmp a b of
Just rel -> pcomp1 as bs rel ordp
_ -> Nothing
pcomp _ _ ordp = Just ordp
-- pcomp [] [] ordp = Just ordp
pcomp1 as bs ordn EQ = pcomp as bs ordn
pcomp1 as bs EQ ordp = pcomp as bs ordp
pcomp1 as bs ordn ordp =
if ordn == ordp then pcomp as bs ordp else Nothing
-- |Part-ordering comparison for Maybe values.
partCompareMaybe :: (Eq a) => Maybe a -> Maybe a -> Maybe Ordering
partCompareMaybe Nothing Nothing = Just EQ
partCompareMaybe (Just _) Nothing = Just GT
partCompareMaybe Nothing (Just _) = Just LT
partCompareMaybe (Just a) (Just b) = if a == b then Just EQ else Nothing
-- |Part-ordering comparison on lists of Maybe values.
partCompareListMaybe :: (Eq a) => [Maybe a] -> [Maybe a] -> Maybe Ordering
partCompareListMaybe = partCompareListPartOrd partCompareMaybe
-- |Part-ordering comparison on lists based on subset relationship
partCompareListSubset :: (Eq a) => [a] -> [a] -> Maybe Ordering
partCompareListSubset a b
| aeqvb = Just EQ
| asubb = Just LT
| bsuba = Just GT
| otherwise = Nothing
where
asubb = a `subset` b
bsuba = b `subset` a
aeqvb = asubb && bsuba
x `subset` y = and [ ma `elem` y | ma <- x ]
------------------------------------------------------------
-- Test cases
------------------------------------------------------------
{-
notTrueFalse = Nothing :: Maybe Bool
-- partCompareListOrd
test01 = partCompareListOrd [1,2,3] [1,2,3] == Just EQ
test02 = partCompareListOrd [1,2,3] [2,3,4] == Just LT
test03 = partCompareListOrd [1,2,4] [1,2,3] == Just GT
test04 = partCompareListOrd [1,2,3] [2,1,3] == Nothing
-- partCompareMaybe
test11 = partCompareMaybe (Just True) (Just True) == Just EQ
test12 = partCompareMaybe (Just True) (Just False) == Nothing
test13 = partCompareMaybe notTrueFalse (Just False) == Just LT
test14 = partCompareMaybe (Just True) notTrueFalse == Just GT
test15 = partCompareMaybe notTrueFalse notTrueFalse == Just EQ
-- partCompareListMaybe
test21 = partCompareListMaybe [Just True,Just False]
[Just True,Just False]
== Just EQ
test22 = partCompareListMaybe [Just True,Just False]
[Just True,Just True]
== Nothing
test23 = partCompareListMaybe [Just False,Just True]
[Just False,Just True]
== Just EQ
test24 = partCompareListMaybe [Nothing, Just True]
[Just False,Just True]
== Just LT
test25 = partCompareListMaybe [Just False,Just True]
[Just False,Nothing]
== Just GT
test26 = partCompareListMaybe [Nothing, Just True]
[Just False,Nothing]
== Nothing
test27 = partCompareListMaybe [Nothing,Just True]
[Nothing,Nothing]
== Just GT
test28 = partCompareListMaybe [notTrueFalse,notTrueFalse]
[notTrueFalse,notTrueFalse]
== Just EQ
-- minima, maxima
test31a = maxima partCompareListMaybe ds1a == ds1b
test31b = minima partCompareListMaybe ds1a == ds1c
ds1a =
[ [Just 'a',Just 'b',Just 'c']
, [Just 'a',Just 'b',Nothing ]
, [Just 'a',Nothing ,Just 'c']
, [Just 'a',Nothing ,Nothing ]
, [Nothing ,Just 'b',Just 'c']
, [Nothing ,Just 'b',Nothing ]
, [Nothing ,Nothing ,Just 'c']
, [Nothing ,Nothing ,Nothing ]
]
ds1b =
[ [Just 'a',Just 'b',Just 'c']
]
ds1c =
[ [Nothing ,Nothing ,Nothing ]
]
test32a = maxima partCompareListMaybe ds2a == ds2b
test32b = minima partCompareListMaybe ds2a == ds2c
ds2a =
[ [Just 'a',Just 'b',Nothing ]
, [Just 'a',Nothing ,Just 'c']
, [Just 'a',Nothing ,Nothing ]
, [Nothing ,Just 'b',Just 'c']
, [Nothing ,Just 'b',Nothing ]
, [Nothing ,Nothing ,Just 'c']
]
ds2b =
[ [Just 'a',Just 'b',Nothing ]
, [Just 'a',Nothing ,Just 'c']
, [Nothing ,Just 'b',Just 'c']
]
ds2c =
[ [Just 'a',Nothing ,Nothing ]
, [Nothing ,Just 'b',Nothing ]
, [Nothing ,Nothing ,Just 'c']
]
test33a = maxima partCompareListMaybe ds3a == ds3b
test33b = minima partCompareListMaybe ds3a == ds3c
ds3a =
[ [Just "a1",Just "b1",Just "c1"]
, [Just "a2",Just "b2",Nothing ]
, [Just "a3",Nothing ,Just "c3"]
, [Just "a4",Nothing ,Nothing ]
, [Nothing ,Just "b5",Just "c5"]
, [Nothing ,Just "b6",Nothing ]
, [Nothing ,Nothing ,Just "c7"]
]
ds3b =
[ [Just "a1",Just "b1",Just "c1"]
, [Just "a2",Just "b2",Nothing ]
, [Just "a3",Nothing ,Just "c3"]
, [Just "a4",Nothing ,Nothing ]
, [Nothing ,Just "b5",Just "c5"]
, [Nothing ,Just "b6",Nothing ]
, [Nothing ,Nothing ,Just "c7"]
]
ds3c =
[ [Just "a1",Just "b1",Just "c1"]
, [Just "a2",Just "b2",Nothing ]
, [Just "a3",Nothing ,Just "c3"]
, [Just "a4",Nothing ,Nothing ]
, [Nothing ,Just "b5",Just "c5"]
, [Nothing ,Just "b6",Nothing ]
, [Nothing ,Nothing ,Just "c7"]
]
test34a = maxima partCompareListMaybe ds4a == ds4b
test34b = minima partCompareListMaybe ds4a == ds4c
ds4a =
[ [Just 1, Just 1 ]
, [Just 2, Nothing]
, [Nothing,Just 3 ]
, [Nothing,Nothing]
]
ds4b =
[ [Just 1, Just 1 ]
, [Just 2, Nothing]
, [Nothing,Just 3 ]
]
ds4c =
[ [Nothing,Nothing]
]
-- Check handling of equal values
test35a = maxima partCompareListMaybe ds5a == ds5b
test35b = minima partCompareListMaybe ds5a == ds5c
ds5a =
[ [Just 1, Just 1 ]
, [Just 2, Nothing]
, [Nothing,Just 3 ]
, [Nothing,Nothing]
, [Just 1, Just 1 ]
, [Just 2, Nothing]
, [Nothing,Just 3 ]
, [Nothing,Nothing]
]
ds5b =
[ [Just 1, Just 1 ]
, [Just 2, Nothing]
, [Nothing,Just 3 ]
]
ds5c =
[ [Nothing,Nothing]
]
-- test case 32 with different ordering of values
test36a = maxima partCompareListMaybe ds6a == ds6b
test36b = minima partCompareListMaybe ds6a == ds6c
ds6a =
[ [Just 'a',Just 'b',Nothing ]
, [Nothing ,Nothing ,Just 'c']
, [Nothing ,Just 'b',Nothing ]
, [Nothing ,Just 'b',Just 'c']
, [Just 'a',Nothing ,Nothing ]
, [Just 'a',Nothing ,Just 'c']
]
ds6b =
[ [Just 'a',Just 'b',Nothing ]
, [Nothing ,Just 'b',Just 'c']
, [Just 'a',Nothing ,Just 'c']
]
ds6c =
[ [Nothing ,Nothing ,Just 'c']
, [Nothing ,Just 'b',Nothing ]
, [Just 'a',Nothing ,Nothing ]
]
test = and
[ test01, test02, test03, test04
, test11, test12, test13, test14, test15
, test21, test22, test23, test24, test25, test26, test27, test28
, test31a, test31b, test32a, test32b, test33a, test33b
, test34a, test34b, test35a, test35b, test36a, test36b
]
-}
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Data/String/ShowLines.hs 0000644 0000000 0000000 00000005676 13543702315 016453 0 ustar 00 0000000 0000000 --------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : ShowLines
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011, 2012 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : H98
--
-- This module defines an extension of the 'Show' class for displaying
-- multi-line values. It serves the following purposes:
--
-- (1) provides a method with greater layout control of multiline values,
--
-- (2) provides a possibility to override the default 'Show' behaviour
-- for programs that use the extended 'ShowLines' interface, and
--
-- (3) uses a 'ShowS' intermediate value to avoid unnecessary
-- concatenation of long strings.
--
--------------------------------------------------------------------------------
module Data.String.ShowLines (ShowLines(..)) where
-- |ShowLines is a type class for values that may be formatted in
-- multi-line displays.
class (Show sh) => ShowLines sh where
-- |Multi-line value display method
--
-- Create a multiline displayable form of a value, returned
-- as a 'ShowS' value. The default implementation behaves just
-- like a normal instance of 'Show'.
--
-- This function is intended to allow the calling function some control
-- of multiline displays by providing:
--
-- (1) the first line of the value is not preceded by any text, so
-- it may be appended to some preceding text on the same line,
--
-- (2) the supplied line break string is used to separate lines of the
-- formatted text, and may include any desired indentation, and
--
-- (3) no newline is output following the final line of text.
showls :: String -> sh -> ShowS
showls _ = shows
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Network/URI/Ord.hs 0000644 0000000 0000000 00000004025 13543702315 015220 0 ustar 00 0000000 0000000 --------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : Ord
-- Copyright : (c) 2012, 2014 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : As it provides nothing, it should be pretty portable
--
-- *This module is deprecated and will be removed*
--
-- This module no-longer does anything since
-- 'Network.URI.URI' now has an 'Ord' instance by
-- default (prior to @network @2.4.0.0@ this was not the case).
--
-- This module used to provide an ordering for earlier versions
-- of the network package, but with the split of 'Network.URI'
-- out to the @network-uri@ in version @2.6.0.0@, the CPP
-- needed to keep this going got annoying enough for me to
-- just require a recent @network@ package.
--
--------------------------------------------------------------------------------
module Network.URI.Ord () where
--------------------------------------------------------------------------------
--
-- Copyright (c) 2014 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish.hs 0000644 0000000 0000000 00000027344 14220136201 013476 0 ustar 00 0000000 0000000 --------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : Swish
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011, 2012, 2020 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : H98
--
-- Swish: Semantic Web Inference Scripting in Haskell
--
-- This program is a simple skeleton for constructing Semantic Web [1]
-- inference tools in Haskell, using the RDF graph and several RDF
-- parsers (at present Notation 3 and NTriples).
--
-- It might be viewed as a kind of embroyonic CWM [2] in Haskell,
-- except that the intent is that Haskell will be used as a primary
-- language for defining inferences. As such, Swish is an open-ended
-- toolkit for constructing new special-purpose Semantic Web
-- applications rather than a closed, self-contained general-purpose
-- SW application. As such, it is part of another experiment along
-- the lines described in [3].
--
-- The script format used by Swish is described in
-- "Swish.Script".
--
-- Users wishing to process RDF data directly may prefer to look at
-- the following modules; "Swish.RDF", "Swish.RDF.Parser.Turtle",
-- "Swish.RDF.Parser.N3", "Swish.RDF.Parser.NTriples",
-- "Swish.RDF.Formatter.Turtle", "Swish.RDF.Formatter.N3"
-- and "Swish.RDF.Formatter.NTriples".
--
-- (1) Semantic web:
--
-- (2) CWM:
--
-- (3) Motivation:
--
-- (4) Notation 3:
--
-- (5) Turtle:
--
-- (6) RDF:
--
-- Notes
--
-- I anticipate that this module may be used as a starting point for
-- creating new programs rather then as a complete program in its own
-- right. The functionality built into this code is selected with a
-- view to testing the Haskell modules for handling RDF rather than
-- for performing any particular application processing (though
-- development as a tool with some broader utility is not ruled out).
--
-- With the following in ghci:
--
-- >>> :m + Swish
-- >>> :set prompt "swish> "
--
-- then we can run a Swish script (format described in "Swish.Script")
-- by saying:
--
-- >>> runSwish "-s=script.ss"
-- ExitSuccess
--
-- or convert a file from Turtle to NTriples format with:
--
-- >>> runSwish "-ttl -i=foo.ttl -nt -o=foo.nt"
-- ExitSuccess
--
-- You can also use `validateCommands` by giving it the individual commands,
-- such as
--
-- >>> let Right cs = validateCommands ["-ttl", "-i=file1.ttl", "-c=file2.ttl"]
-- >>> cs
-- [SwishAction: -ttl,SwishAction: -i=file1.ttl,SwishAction: -c=file2.ttl]
-- >>> st <- runSwishActions cs
-- >>> st
-- The graphs do not compare as equal.
--
--------------------------------------------------------------------------------
module Swish ( SwishStatus(..)
, SwishAction
, runSwish
, runSwishActions
, displaySwishHelp
, splitArguments
, validateCommands
) where
import Swish.Commands
( swishFormat
, swishBase
, swishInput
, swishOutput
, swishMerge
, swishCompare
, swishGraphDiff
, swishScript
)
import Swish.Monad (SwishStateIO, SwishState(..), SwishStatus(..)
, SwishFormat(..)
, emptyState)
import Swish.QName (qnameFromURI)
import Control.Monad.State (execStateT)
import Network.URI (parseURI)
import Data.Char (isSpace)
import Data.Either (partitionEithers)
import System.Exit (ExitCode(ExitSuccess, ExitFailure))
------------------------------------------------------------
-- Command line description
------------------------------------------------------------
-- we do not display the version in the help file to avoid having
-- to include the Paths_swish module (so that we can use this from
-- an interactive environment).
--
usageText :: [String]
usageText =
[ "Swish: Read, merge, write, compare and process RDF graphs."
, ""
, "Usage: swish option option ..."
, ""
, "where the options are processed from left to right, and may be"
, "any of the following:"
, "-h display this message."
, "-? display this message."
, "-v display Swish version and quit."
, "-q do not display Swish version on start up."
, "-nt use Ntriples format for subsequent input and output."
, "-ttl use Turtle format for subsequent input and output."
, "-n3 use Notation3 format for subsequent input and output (default)"
, "-i[=file] read file in selected format into the graph workspace,"
, " replacing any existing graph."
, "-m[=file] merge file in selected format with the graph workspace."
, "-c[=file] compare file in selected format with the graph workspace."
, "-d[=file] show graph differences between the file in selected"
, " format and the graph workspace. Differences are displayed"
, " to the standard output stream."
, "-o[=file] write the graph workspace to a file in the selected format."
, "-s[=file] read and execute Swish script commands from the named file."
, "-b[=base] set or clear the base URI. The semantics of this are not"
, " fully defined yet."
, ""
, " If an optional filename value is omitted, the standard input"
, " or output stream is used, as appropriate."
, ""
, "Exit status codes:"
, "Success - operation completed successfully/graphs compare equal"
, "1 - graphs compare different"
, "2 - input data format error"
, "3 - file access problem"
, "4 - command line error"
, "5 - script file execution error"
, ""
, "Examples:"
, ""
, "swish -i=file"
, " read file as Notation3, and report any syntax errors."
, "swish -i=file1 -o=file2"
, " read file1 as Notation3, report any syntax errors, and output the"
, " resulting graph as reformatted Notation3 (the output format"
, " is not perfect but may be improved)."
, "swish -nt -i=file -n3 -o"
, " read file as NTriples and output as Notation3 to the screen."
, "swich -i=file1 -c=file2"
, " read file1 and file2 as notation3, report any syntax errors, and"
, " if both are OK, compare the resulting graphs to indicate whether"
, " or not they are equivalent."
]
-- | Write out the help for Swish
displaySwishHelp :: IO ()
displaySwishHelp = mapM_ putStrLn usageText
------------------------------------------------------------
-- Swish command line interpreter
------------------------------------------------------------
--
-- This is a composite monad combining some state with an IO
-- Monad. lift allows a pure IO monad to be used as a step
-- of the computation.
--
-- | Return any arguments that need processing immediately, namely
-- the \"help\", \"quiet\" and \"version\" options.
--
splitArguments :: [String] -> ([String], [String])
splitArguments = partitionEithers . map splitArgument
splitArgument :: String -> Either String String
splitArgument "-?" = Left "-h"
splitArgument "-h" = Left "-h"
splitArgument "-v" = Left "-v"
splitArgument "-q" = Left "-q"
splitArgument x = Right x
-- | Represent a Swish action. At present there is no way to create these
-- actions other than 'validateCommands'.
--
newtype SwishAction = SA (String, SwishStateIO ())
instance Show SwishAction where
show (SA (lbl,_)) = "SwishAction: " ++ lbl
-- | Given a list of command-line arguments create the list of actions
-- to perform or a string and status value indicating an input error.
validateCommands :: [String] -> Either (String, SwishStatus) [SwishAction]
validateCommands args =
let (ls, rs) = partitionEithers (map validateCommand args)
in case ls of
(e:_) -> Left e
[] -> Right rs
-- This allows you to say "-nt=foo" and currently ignores the values
-- passed through. This may change
--
validateCommand :: String -> Either (String, SwishStatus) SwishAction
validateCommand cmd =
let (nam,more) = break (== '=') cmd
arg = drop 1 more
marg = if null arg then Nothing else Just arg
wrap f = Right $ SA (cmd, f marg)
wrap1 f = Right $ SA (cmd, f)
in case nam of
"-ttl" -> wrap1 $ swishFormat Turtle
"-nt" -> wrap1 $ swishFormat NT
"-n3" -> wrap1 $ swishFormat N3
"-i" -> wrap swishInput
"-m" -> wrap swishMerge
"-c" -> wrap swishCompare
"-d" -> wrap swishGraphDiff
"-o" -> wrap swishOutput
"-b" -> validateBase cmd marg
"-s" -> wrap swishScript
_ -> Left ("Invalid command line argument: " ++ cmd, SwishArgumentError)
-- | Execute the given set of actions.
swishCommands :: [SwishAction] -> SwishStateIO ()
swishCommands = mapM_ swishCommand
-- | Execute an action.
swishCommand :: SwishAction -> SwishStateIO ()
swishCommand (SA (_,act)) = act
validateBase :: String -> Maybe String -> Either (String, SwishStatus) SwishAction
validateBase arg Nothing = Right $ SA (arg, swishBase Nothing)
validateBase arg (Just b) =
case parseURI b >>= qnameFromURI of
j@(Just _) -> Right $ SA (arg, swishBase j)
_ -> Left ("Invalid base URI <" ++ b ++ ">", SwishArgumentError)
------------------------------------------------------------
-- Interactive test function (e.g. for use in ghci)
------------------------------------------------------------
-- this ignores the "flags" options, namely
-- -q / -h / -? / -v
-- | Parse and run the given string as if given at the command
-- line. The \"quiet\", \"version\" and \"help\" options are
-- ignored.
--
runSwish :: String -> IO ExitCode
runSwish cmdline = do
let args = breakAll isSpace cmdline
(_, cmds) = splitArguments args
case validateCommands cmds of
Left (emsg, ecode) -> do
putStrLn $ "Swish exit: " ++ emsg
return $ ExitFailure $ fromEnum ecode
Right acts -> do
ec <- runSwishActions acts
case ec of
SwishSuccess -> return ExitSuccess
_ -> do
putStrLn $ "Swish exit: " ++ show ec
return $ ExitFailure $ fromEnum ec
-- |Break list into a list of sublists, separated by element
-- satisfying supplied condition.
breakAll :: (a -> Bool) -> [a] -> [[a]]
breakAll _ [] = []
breakAll p s = let (h,s') = break p s
in h : breakAll p (drop 1 s')
-- | Execute the given set of actions.
runSwishActions :: [SwishAction] -> IO SwishStatus
runSwishActions acts = exitcode `fmap` execStateT (swishCommands acts) emptyState
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012, 2020 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/Commands.hs 0000644 0000000 0000000 00000032507 14220136201 015234 0 ustar 00 0000000 0000000 {-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
--------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : Commands
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011, 2012, 2014, 2020 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : FlexibleContexts, OverloadedStrings
--
-- Functions to deal with indivudual Swish command options.
--
--------------------------------------------------------------------------------
module Swish.Commands
( swishFormat
, swishBase
-- , swishVerbose
, swishInput
, swishOutput
, swishMerge
, swishCompare
, swishGraphDiff
, swishScript
)
where
import Swish.GraphClass (LDGraph(..), Label(..))
import Swish.GraphPartition (GraphPartition(..))
import Swish.GraphPartition (partitionGraph, comparePartitions, partitionShowP)
import Swish.Monad (SwishStateIO, SwishState(..)
, SwishStatus(..), SwishFormat(..)
, setFormat, setBase, setGraph, resetInfo
, resetError, setStatus, swishError, reportLine)
import Swish.QName (QName, qnameFromURI, qnameFromFilePath, getQNameURI)
import Swish.Script (parseScriptFromText)
import Swish.RDF.Graph (RDFGraph, merge)
import qualified Swish.RDF.Formatter.Turtle as TTLF
import qualified Swish.RDF.Formatter.N3 as N3F
import qualified Swish.RDF.Formatter.NTriples as NTF
import Swish.RDF.Parser.Turtle (parseTurtle)
import Swish.RDF.Parser.N3 (parseN3)
import Swish.RDF.Parser.NTriples (parseNT)
import Swish.RDF.Parser.Utils (appendURIs)
import System.IO
( Handle, IOMode(..)
, hPutStr, hPutStrLn, hClose
, hIsReadable, hIsWritable
, openFile, stdin, stdout
)
import Network.URI (parseURIReference)
import Control.Monad.Trans (MonadTrans(..))
import Control.Monad.State (modify, gets)
import Control.Monad (when)
import qualified Data.Set as S
import qualified Data.Text.Lazy as T
import qualified Data.Text.Lazy.IO as IO
import Data.Maybe (isJust, fromMaybe)
import Control.Exception as CE
-- | Set the file format.
--
swishFormat :: SwishFormat -> SwishStateIO ()
swishFormat = modify . setFormat
-- | Set (or clear) the base URI.
swishBase :: Maybe QName -> SwishStateIO ()
swishBase = modify . setBase
-- | Read in a graph and make it the current graph.
swishInput ::
Maybe String -- ^ A filename or, if 'Nothing', then use standard input.
-> SwishStateIO ()
swishInput fnam =
swishReadGraph fnam >>= maybe (return ()) (modify . setGraph)
-- | Read in a graph and merge it with the current graph.
swishMerge ::
Maybe String -- ^ A filename or, if 'Nothing', then use standard input.
-> SwishStateIO ()
swishMerge fnam =
swishReadGraph fnam >>= maybe (return ()) (modify . mergeGraph)
mergeGraph :: RDFGraph -> SwishState -> SwishState
mergeGraph gr state = state { graph = newgr }
where
newgr = merge gr (graph state)
-- | Read in a graph and compare it with the current graph.
swishCompare ::
Maybe String -- ^ A filename or, if 'Nothing', then use standard input.
-> SwishStateIO ()
swishCompare fnam =
swishReadGraph fnam >>= maybe (return ()) compareGraph
compareGraph :: RDFGraph -> SwishStateIO ()
compareGraph gr = do
oldGr <- gets graph
let exitCode = if gr == oldGr then SwishSuccess else SwishGraphCompareError
modify $ setStatus exitCode
------------------------------------------------------------
-- Display graph differences from named file
------------------------------------------------------------
-- | Read in a graph and display the differences to the current
-- graph to standard output.
swishGraphDiff ::
Maybe String -- ^ A filename or, if 'Nothing', then use standard input.
-> SwishStateIO ()
swishGraphDiff fnam =
swishReadGraph fnam >>= maybe (return ()) diffGraph
diffGraph :: RDFGraph -> SwishStateIO ()
diffGraph gr = do
oldGr <- gets graph
let p1 = partitionGraph (S.toList $ getArcs oldGr)
p2 = partitionGraph (S.toList $ getArcs gr)
diffs = comparePartitions p1 p2
swishWriteFile (swishOutputDiffs diffs) Nothing
swishOutputDiffs :: (Label lb) =>
[(Maybe (GraphPartition lb),Maybe (GraphPartition lb))]
-> Maybe String
-> Handle
-> SwishStateIO ()
swishOutputDiffs diffs fnam hnd = do
lift $ hPutStrLn hnd ("Graph differences: " ++ show (length diffs))
mapM_ (swishOutputDiff fnam hnd) (zip [1..] diffs)
swishOutputDiff :: (Label lb) =>
Maybe String
-> Handle
-> (Int,(Maybe (GraphPartition lb),Maybe (GraphPartition lb)))
-> SwishStateIO ()
swishOutputDiff fnam hnd (diffnum,(part1,part2)) = do
lift $ hPutStrLn hnd ("---- Difference " ++ show diffnum ++ " ----")
lift $ hPutStr hnd "Graph 1:"
swishOutputPart fnam hnd part1
lift $ hPutStr hnd "Graph 2:"
swishOutputPart fnam hnd part2
swishOutputPart :: (Label lb) =>
Maybe String
-> Handle
-> Maybe (GraphPartition lb)
-> SwishStateIO ()
swishOutputPart _ hnd part =
let out = maybe "\n(No arcs)" (partitionShowP "\n") part
in lift $ hPutStrLn hnd out
------------------------------------------------------------
-- Execute script from named file
------------------------------------------------------------
-- | Read in a script and execute it.
swishScript ::
Maybe String -- ^ A filename or, if 'Nothing', then use standard input.
-> SwishStateIO ()
swishScript fnam = swishReadScript fnam >>= mapM_ swishCheckResult
swishReadScript :: Maybe String -> SwishStateIO [SwishStateIO ()]
swishReadScript = swishReadFile swishParseScript []
{-|
Calculate the base URI to use; it combines the file name
with any user-supplied base.
If both the file name and user-supplied base are Nothing
then the value
http://id.ninebynine.org/2003/Swish/
is used.
Needs some work.
-}
defURI :: QName
defURI = "http://id.ninebynine.org/2003/Swish/"
calculateBaseURI ::
Maybe FilePath -- ^ file name
-> SwishStateIO QName -- ^ base URI
calculateBaseURI Nothing = gets (fromMaybe defURI . base)
calculateBaseURI (Just fnam) =
case parseURIReference fnam of
Just furi -> do
mbase <- gets base
case mbase of
Just buri -> case appendURIs (getQNameURI buri) furi of
Left emsg -> fail emsg -- TODO: think about this ...
Right res -> return $ fromMaybe defURI (qnameFromURI res)
Nothing -> lift $ qnameFromFilePath fnam
Nothing -> fail $ "Unable to convert to URI: filepath=" ++ fnam
swishParseScript ::
Maybe String -- file name (or "stdin" if Nothing)
-> T.Text -- script contents
-> SwishStateIO [SwishStateIO ()]
swishParseScript mfpath inp = do
buri <- calculateBaseURI mfpath
case parseScriptFromText (Just buri) inp of
Left err -> do
let inName = maybe "standard input" ("file " ++) mfpath
swishError ("Script syntax error in " ++ inName ++ ": " ++ err) SwishDataInputError
return []
Right scs -> return scs
swishCheckResult :: SwishStateIO () -> SwishStateIO ()
swishCheckResult swishcommand = do
swishcommand
er <- gets errormsg
case er of
Just x -> swishError x SwishExecutionError >> modify resetError
_ -> return ()
ms <- gets infomsg
case ms of
Just x -> reportLine x >> modify resetInfo
_ -> return ()
-- | Write out the current graph.
swishOutput ::
Maybe String -- ^ A filename or, if 'Nothing', then use standard output.
-> SwishStateIO ()
swishOutput = swishWriteFile swishOutputGraph
swishOutputGraph :: Maybe String -> Handle -> SwishStateIO ()
swishOutputGraph _ hnd = do
fmt <- gets format
let writeOut formatter = do
out <- gets $ formatter . graph
lift $ IO.hPutStrLn hnd out
case fmt of
N3 -> writeOut N3F.formatGraphAsLazyText
NT -> writeOut NTF.formatGraphAsLazyText
Turtle -> writeOut TTLF.formatGraphAsLazyText
-- _ -> swishError ("Unsupported file format: "++show fmt) SwishArgumentError
------------------------------------------------------------
-- Common input functions
------------------------------------------------------------
--
-- Keep the logic separate for reading file data and
-- parsing it to an RDF graph value.
swishReadGraph :: Maybe String -> SwishStateIO (Maybe RDFGraph)
swishReadGraph = swishReadFile swishParse Nothing
-- | Open a file (or stdin), read its contents, and process them.
--
swishReadFile ::
(Maybe String -> T.Text -> SwishStateIO a) -- ^ Convert filename and contents into desired value
-> a -- ^ the value to use if the file can not be read in
-> Maybe String -- ^ the file name or @stdin@ if @Nothing@
-> SwishStateIO a
swishReadFile conv errVal fnam =
let reader (h,f,i) = do
res <- conv fnam i
when f $ lift $ hClose h -- given that we use IO.hGetContents not sure the close is needed
return res
in swishOpenFile fnam >>= maybe (return errVal) reader
-- open a file in the SwishStateIO monad, catching
-- any errors
--
sOpen :: FilePath -> IOMode -> SwishStateIO (Either IOError Handle)
sOpen fp fm = lift . CE.try $ openFile fp fm
-- | Open and read file, returning its handle and content, or Nothing
-- WARNING: the handle must not be closed until input is fully evaluated
--
swishOpenFile :: Maybe String -> SwishStateIO (Maybe (Handle, Bool, T.Text))
swishOpenFile Nothing = readFromHandle stdin Nothing
swishOpenFile (Just fnam) = do
o <- sOpen fnam ReadMode
case o of
Left _ -> do
swishError ("Cannot open file: " ++ fnam) SwishDataAccessError
return Nothing
Right hnd -> readFromHandle hnd $ Just ("file: " ++ fnam)
readFromHandle :: Handle -> Maybe String -> SwishStateIO (Maybe (Handle, Bool, T.Text))
readFromHandle hdl mlbl = do
hrd <- lift $ hIsReadable hdl
if hrd
then do
fc <- lift $ IO.hGetContents hdl
return $ Just (hdl, isJust mlbl, fc)
else do
lbl <- case mlbl of
Just l -> lift (hClose hdl) >> return l
Nothing -> return "standard input"
swishError ("Cannot read from " ++ lbl) SwishDataAccessError
return Nothing
swishParse ::
Maybe String -- ^ filename (if not stdin)
-> T.Text -- ^ contents of file
-> SwishStateIO (Maybe RDFGraph)
swishParse mfpath inp = do
fmt <- gets format
buri <- calculateBaseURI mfpath
let toError eMsg =
swishError (show fmt ++ " syntax error in " ++ inName ++ ": " ++ eMsg) SwishDataInputError
>> return Nothing
inName = maybe "standard input" ("file " ++) mfpath
readIn reader = case reader inp of
Left eMsg -> toError eMsg
Right res -> return $ Just res
case fmt of
Turtle -> readIn (`parseTurtle` Just (getQNameURI buri))
N3 -> readIn (`parseN3` Just buri)
NT -> readIn parseNT
{-
_ -> swishError ("Unsupported file format: "++show fmt) SwishArgumentError >>
return Nothing
-}
swishWriteFile ::
(Maybe String -> Handle -> SwishStateIO ()) -- ^ given a file name and a handle, write to it
-> Maybe String
-> SwishStateIO ()
swishWriteFile conv fnam =
let hdlr (h, c) = conv fnam h >> when c (lift $ hClose h)
in swishCreateWriteableFile fnam >>= maybe (return ()) hdlr
-- | Open file for writing, returning its handle, or Nothing
-- Also returned is a flag indicating whether or not the
-- handled should be closed when writing is done (if writing
-- to standard output, the handle should not be closed as the
-- run-time system should deal with that).
swishCreateWriteableFile :: Maybe String -> SwishStateIO (Maybe (Handle,Bool))
swishCreateWriteableFile Nothing = do
hwt <- lift $ hIsWritable stdout
if hwt
then return $ Just (stdout, False)
else do
swishError "Cannot write to standard output" SwishDataAccessError
return Nothing
swishCreateWriteableFile (Just fnam) = do
o <- sOpen fnam WriteMode
case o of
Left _ -> do
swishError ("Cannot open file for writing: " ++ fnam) SwishDataAccessError
return Nothing
Right hnd -> do
hwt <- lift $ hIsWritable hnd
if hwt
then return $ Just (hnd, True)
else do
lift $ hClose hnd
swishError ("Cannot write to file: " ++ fnam) SwishDataAccessError
return Nothing
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012, 2014, 2020 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/Datatype.hs 0000644 0000000 0000000 00000125076 14220136201 015252 0 ustar 00 0000000 0000000 {-# LANGUAGE CPP #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE OverloadedStrings #-}
--------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : Datatype
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012, 2018, 2019, 2022 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : ExistentialQuantification, OverloadedStrings
--
-- This module defines the structures used to represent and
-- manipulate datatypes. It is designed as a basis for handling datatyped
-- RDF literals, but the functions in this module are more generic.
--
--------------------------------------------------------------------------------
-- Testing note: this module supports a number of specific datatypes.
-- It is intended that functionality in this module will be tested via
-- modules "Swish.RDF.RDFDatatype",
-- "Swish.RDF.ClassRestrictionRule" and
-- "Swish.RDF.RDFDatatypeXsdInteger".
-- See also module ClassRestrictionRuleTest for test cases.
module Swish.Datatype
( Datatype(..)
, typeName, typeRules, typeMkRules, typeMkModifiers, typeMkCanonicalForm
, getTypeAxiom, getTypeRule
, DatatypeVal(..)
, getDTMod
, getDTRel
, tvalMkCanonicalForm
, DatatypeMap(..)
, DatatypeRel(..), DatatypeRelFn, DatatypeRelPr
, altArgs
, UnaryFnDescr, UnaryFnTable, UnaryFnApply, unaryFnApp
, BinaryFnDescr, BinaryFnTable, BinaryFnApply, binaryFnApp
, BinMaybeFnDescr, BinMaybeFnTable, BinMaybeFnApply, binMaybeFnApp
, ListFnDescr, ListFnTable, ListFnApply, listFnApp
, DatatypeMod(..), ModifierFn
, ApplyModifier
, nullDatatypeMod
-- , applyDatatypeMod
, makeVmod11inv, makeVmod11
, makeVmod21inv, makeVmod21
, makeVmod20
, makeVmod22
, makeVmodN1
, DatatypeSub(..)
)
where
import Swish.Namespace (ScopedName)
import Swish.Rule (Formula(..), Rule(..))
import Swish.Ruleset (Ruleset(..))
import Swish.Ruleset (getRulesetAxiom, getRulesetRule)
import Swish.VarBinding (VarBinding(..), VarBindingModify(..), OpenVarBindingModify)
import Swish.VarBinding (addVarBinding, nullVarBindingModify)
import Swish.RDF.Vocabulary (swishName)
import Swish.Utils.ListHelpers (flist)
-- used to add Show instances for structures during debugging
-- but backed out again.
--
-- import Swish.Utils.ShowM (ShowM(..))
#if (!defined(__GLASGOW_HASKELL__)) || (__GLASGOW_HASKELL__ < 808)
import Control.Applicative ((<$>))
#endif
import Data.Maybe (isJust, catMaybes)
import qualified Data.Map as M
import qualified Data.Text as T
------------------------------------------------------------
-- Datatype framework
------------------------------------------------------------
-- |Datatype wraps a 'DatatypeVal' value, hiding the value type that
-- is used only in implementations of the datatype.
-- Users see just the datatype name and associated ruleset.
--
data Datatype ex lb vn = forall vt . Datatype (DatatypeVal ex vt lb vn)
-- |Get type name from Datatype value
typeName :: Datatype ex lb vn -> ScopedName
typeName (Datatype dtv) = tvalName dtv
-- |Get static rules from Datatype value
typeRules :: Datatype ex lb vn -> Ruleset ex
typeRules (Datatype dtv) = tvalRules dtv
-- |Make rules for Datatype value based on supplied expression
typeMkRules :: Datatype ex lb vn -> ex -> [Rule ex]
typeMkRules (Datatype dtv) = tvalMkRules dtv
-- |Make variable binding modifiers based on values supplied
typeMkModifiers :: Datatype ex lb vn -> [OpenVarBindingModify lb vn]
typeMkModifiers (Datatype dtv) = tvalMkMods dtv
-- |Get the named axiom from a Datatype value.
getTypeAxiom :: ScopedName -> Datatype ex lb vn -> Maybe (Formula ex)
getTypeAxiom nam dt = getRulesetAxiom nam (typeRules dt)
-- |Get the named rule from a Datatype value.
getTypeRule :: ScopedName -> Datatype ex lb vn -> Maybe (Rule ex)
getTypeRule nam dt = getRulesetRule nam (typeRules dt)
-- |Get the canonical form of a datatype value.
typeMkCanonicalForm :: Datatype ex lb vn -> T.Text -> Maybe T.Text
typeMkCanonicalForm (Datatype dtv) = tvalMkCanonicalForm dtv
------------------------------------------------------------
-- DatatypeVal
------------------------------------------------------------
-- |DatatypeVal is a structure that defines a number of functions
-- and values that characterize the behaviour of a datatype.
--
-- A datatype is specified with respect to (polymophic in) a given
-- type of (syntactic) expression with which it may be used, and
-- a value type (whose existence is hidden as an existential type
-- within `DatatypeMap`).
--
-- (I tried hiding the value type with an internal existential
-- declaration, but that wouldn't wash. Hence this two-part
-- structure with `Datatype` in which the internal detail
-- of the value type is hidden from users of the `Datatype` class.)
--
-- The datatype characteristic functions have two goals:
--
-- (1) to support the general datatype entailment rules defined by
-- the RDF semantics specification, and
--
-- (2) to define additional datatype-specific inference patterns by
-- means of which provide additional base functionality to
-- applications based on RDF inference.
--
-- Datatype-specific inferences are provided using the `DatatypeRel`
-- structure for a datatype, which allows a number of named relations
-- to be defined on datatype values, and provides mechanisms to
-- calculate missing values in a partially-specified member of
-- a relation.
--
-- Note that rules and variable binding modifiers that deal with
-- combined values of more than one datatype may be defined
-- separately. Definitions in this module are generally applicable
-- only when using a single datatype.
--
-- An alternative model for datatype value calculations is inspired
-- by that introduced by CWM for arithmetic operations, e.g.
--
-- > (1 2 3) math:sum ?x => ?x rdf:value 6
--
-- (where the bare integer @n@ here is shorthand for @\"n\"^^xsd:integer@).
--
-- Datatype-specific inference patterns are provided in two ways:
--
-- * by variable binding modifiers that can be combined with the
-- query results during forward- for backward-chaining of
-- inference rules, and
--
-- * by the definition of inference rulesets that involve
-- datatype values.
--
-- I believe the first method to be more flexible than the second,
-- in that it more readily supports forward and backward chaining,
-- but can be used only through the definition of new rules.
--
-- Type parameters:
--
-- [@ex@] is the type of expression with which the datatype may be used.
--
-- [@vt@] is the internal value type with which the labels are associated.
--
-- [@lb@] is the type of label that may be used as a variable in an
-- expression or rule.
--
-- [@vn@] is the type of node that may be used to carry a value in an
-- expression or rule.
--
data DatatypeVal ex vt lb vn = DatatypeVal
{ tvalName :: ScopedName
-- ^Identifies the datatype, and also
-- its value space class.
, tvalRules :: Ruleset ex
-- ^A set of named expressions and rules
-- that are valid in in any theory that
-- recognizes the current datatype.
, tvalMkRules :: ex -> [Rule ex]
-- ^A function that accepts an expression
-- and devives some datatype-dependent
-- rules from it. This is provided as a
-- hook for creating datatyped class
-- restriction rules.
, tvalMkMods :: [OpenVarBindingModify lb vn]
-- ^Constructs a list of open variable
-- binding modifiers based on tvalMod,
-- but hiding the actual value type.
, tvalMap :: DatatypeMap vt
-- ^Lexical to value mapping, where @vt@ is
-- a datatype used within a Haskell program
-- to represent and manipulate values in
-- the datatype's value space
, tvalRel :: [DatatypeRel vt]
-- ^A set of named relations on datatype
-- values. Each relation accepts a list
-- of @Maybe vt@, and computes any
-- unspecified values that are in the
-- relation with values supplied.
, tvalMod :: [DatatypeMod vt lb vn]
-- ^A list of named values that are used to
-- construct variable binding modifiers, which
-- in turn may be used by a rule definition.
--
-- TODO: In due course, this value may be
-- calculated automatically from the supplied
-- value for @tvalRel@.
}
{-
instance ShowM ex => Show (DatatypeVal ex vt lb vn) where
show dv = "DatatypeVal: " ++ show (tvalName dv) ++ "\n -> rules:\n" ++ show (tvalRules dv)
-}
-- Other accessor functions
-- | Return the named datatype relation, if it exists.
getDTRel ::
ScopedName -> DatatypeVal ex vt lb vn -> Maybe (DatatypeRel vt)
getDTRel nam dtv =
let m = M.fromList $ map (\n -> (dtRelName n, n)) (tvalRel dtv)
in M.lookup nam m
-- | Return the named datatype value modifier, if it exists.
getDTMod ::
ScopedName -> DatatypeVal ex vt lb vn -> Maybe (DatatypeMod vt lb vn)
getDTMod nam dtv =
let m = M.fromList $ map (\n -> (dmName n, n)) (tvalMod dtv)
in M.lookup nam m
-- |Get the canonical form of a datatype value, or @Nothing@.
--
tvalMkCanonicalForm :: DatatypeVal ex vt lb vn -> T.Text -> Maybe T.Text
tvalMkCanonicalForm dtv str = can
where
dtmap = tvalMap dtv
val = mapL2V dtmap str
can = mapV2L dtmap =<< val
-- |DatatypeMap consists of methods that perform lexical-to-value
-- and value-to-canonical-lexical mappings for a datatype.
--
-- The datatype mappings apply to string lexical forms which
-- are stored as `Data.Text`.
--
data DatatypeMap vt = DatatypeMap
{ mapL2V :: T.Text -> Maybe vt
-- ^ Function to map a lexical string to
-- the datatype value. This effectively
-- defines the lexical space of the
-- datatype to be all strings for which
-- yield a value other than @Nothing@.
, mapV2L :: vt -> Maybe T.Text
-- ^ Function to map a value to its canonical
-- lexical form, if it has such.
}
-- |Type for a datatype relation inference function.
--
-- A datatype relation defines tuples of values that satisfy some
-- relation. A datatype relation inference function calculates
-- values that complete a relation with values supplied.
--
-- The function accepts a list of @Maybe vt@, where vt is the
-- datatype value type. It returns one of:
--
-- * Just a list of lists, where each inner list returned is a
-- complete set of values, including the values supplied, that
-- are in the relation.
--
-- * Just an empty list is returned if the supplied values are
-- insufficient to compute any complete sets of values in the
-- relation.
--
-- * Nothing if the supplied values are not consistent with
-- the relation.
--
type DatatypeRelFn vt = [Maybe vt] -> Maybe [[vt]]
-- |Type for datatype relation predicate: accepts a list of values
-- and determines whether or not they satisfy the relation.
--
type DatatypeRelPr vt = [vt] -> Bool
-- |Datatype for a named relation on values of a datatype.
--
data DatatypeRel vt = DatatypeRel
{ dtRelName :: ScopedName
, dtRelFunc :: DatatypeRelFn vt
}
-- |Datatype value modifier functions type
--
-- Each function accepts a list of values and returns a list of values.
-- The exact significance of the different values supplied and returned
-- depends on the variable binding pattern used (cf. 'ApplyModifier'),
-- but in all cases an empty list returned means that the corresponding
-- inputs are not consistent with the function and cannot be used.
--
type ModifierFn vn = [vn] -> [vn]
-- |Type of function used to apply a data value modifier to specified
-- variables in a supplied variable binding. It also accepts the
-- name of the datatype modifier and carries it into the resulting
-- variable binding modifier.
--
-- (Note that @vn@ is not necessarily the same as @vt@, the datatype value
-- type: the modifier functions may be lifted or otherwise adapted
-- to operate on some other type from which the raw data values are
-- extracted.)
--
type ApplyModifier lb vn =
ScopedName -> [ModifierFn vn] -> OpenVarBindingModify lb vn
-- |Wrapper for data type variable binding modifier included in
-- a datatype value.
--
data DatatypeMod vt lb vn = DatatypeMod
{ dmName :: ScopedName
, dmModf :: [ModifierFn vt]
, dmAppf :: ApplyModifier lb vn
}
-- |Null datatype value modifier
nullDatatypeMod :: DatatypeMod vt lb vn
nullDatatypeMod = DatatypeMod
{ dmName = swishName "nullDatatypeMod"
, dmModf = []
, dmAppf = nullAppf
}
where
-- nullAppf :: ScopedName -> [ModifierFn vn] -> OpenVarBindingModify lb vn
nullAppf nam _ lbs = (nullVarBindingModify lbs) { vbmName = nam }
{-
-- |Apply datatype variable binding modifier value to list of labels and
-- a variable binding.
applyDatatypeMod :: (Eq lb, Show lb, Eq vn, Show vn)
=> DatatypeMod vt lb vn -> OpenVarBindingModify lb vn
applyDatatypeMod dtmod = dmAppf dtmod (dmName dtmod) (dmModf dtmod)
-}
{-
dmName dtmod :: ScopedName
dmModf dtmod :: [ModifierFn vt]
:: [[vt] -> [vt]]
dmAppf dtmod :: ApplyModifier lb vn
:: ScopedName -> [ModifierFn vn] -> OpenVarBindingModify lb vn
:: ScopedName -> [[vn] -> [vn]] -> OpenVarBindingModify lb vn
dmAppf dtmod (dmName dtmod)
:: [[vn] -> [vn]] -> OpenVarBindingModify lb vn
-}
--------------------------------------------------------------
-- Functions for creating datatype variable binding modifiers
--------------------------------------------------------------
-- |'ApplyModifier' function for use with 'DatatypeMod' in cases
-- when the value mapping is a @1->1@ function and inverse, such
-- as negate.
--
-- [@nam@] is the name from the 'DatatypeMod' value that is carried into
-- the resulting variable binding modifier.
--
-- [@fns@] are functions used to implement details of the variable
-- binding modifier:
--
-- (0) is @[x,y] -> [?]@, used as a filter (i.e. not creating any
-- new variable bindings), returning a non-empty list if @x@ and @y@
-- are in the appropriate relationship.
--
-- (1) is @[y] -> [x]@, used to perform the calculation in a forward
-- direction.
--
-- (2) is @[x] -> [y]@, used to perform the calculation in a backward
-- direction. This may be the same as (2) (e.g. for negation)
-- or may be different (e.g. increment).
--
-- [@lbs@] is a list of specific label values for which a variable binding
-- modifier will be generated. (The intent is that a variable-free
-- value can be generated as a Curried function, and instantiated
-- for particular variables as required.)
--
-- Note: an irrefutable pattern match for @lbs@ is used so that a name
-- for the 'VarBindingModify' value can be extracted using an undefined
-- label value.
--
makeVmod11inv :: (Ord lb, Ord vn) => ApplyModifier lb vn
makeVmod11inv nam [f0,f1,f2] lbs@(~[lb1,lb2]) = VarBindingModify
{ vbmName = nam
, vbmApply = concatMap app1
, vbmVocab = lbs
, vbmUsage = [[],[lb1],[lb2]]
}
where
app1 vbind = app2 (map (vbMap vbind) lbs) vbind
app2 [Just v1,Just v2] vbind = selv (f0 [v1,v2]) vbind
app2 [Nothing,Just v2] vbind = addv lb1 (f1 [v2]) vbind
app2 [Just v1,Nothing] vbind = addv lb2 (f2 [v1]) vbind
app2 _ _ = []
makeVmod11inv _ _ _ =
error "makeVmod11inv: requires 3 functions and 2 labels"
-- |'ApplyModifier' function for use with 'DatatypeMod' in cases when
-- the value mapping is a non-invertable @1->1@ injection, such as
-- absolute value.
--
-- [@nam@] is the name from the 'DatatypeMod' value that is carried into
-- the resulting variable binding modifier.
--
-- [@fns@] are functions used to implement details of the variable
-- binding modifier:
--
-- (0) is @[x,y] -> [?]@, used as a filter (i.e. not creating any
-- new variable bindings), returning a non-empty list if @x@ and @y@
-- are in the appropriate relationship.
--
-- (1) is @[x]@ -> @[y]@, used to perform the calculation.
--
-- [@lbs@] is a list of specific label values for which a variable binding
-- modifier will be generated.
--
-- Note: an irrefutable pattern match for @lbs@ is used so that a name
-- for the 'VarBindingModify' value can be extracted using an undefined
-- label value.
--
makeVmod11 :: (Ord lb, Ord vn) => ApplyModifier lb vn
makeVmod11 nam [f0,f1] lbs@(~[lb1,_]) = VarBindingModify
{ vbmName = nam
, vbmApply = concatMap app1
, vbmVocab = lbs
, vbmUsage = [[],[lb1]]
}
where
app1 vbind = app2 (map (vbMap vbind) lbs) vbind
app2 [Just v1,Just v2] vbind = selv (f0 [v1,v2]) vbind
app2 [Nothing,Just v2] vbind = addv lb1 (f1 [v2]) vbind
app2 _ _ = []
makeVmod11 _ _ _ =
error "makeVmod11: requires 2 functions and 2 labels"
-- |'ApplyModifier' function for use with 'DatatypeMod' in cases
-- when the value mapping is a @2->1@ invertable function, such as
-- addition or subtraction.
--
-- [@nam@] is the name from the 'DatatypeMod' value that is carried into
-- the resulting variable binding modifier.
--
-- [@fns@] are functions used to implement details of the variable
-- binding modifier:
--
-- (1) is @[x,y,z] -> [?]@, used as a filter (i.e. not creating any
-- new variable bindings), returning a non-empty list if
-- @x@, @y@ and @z@ are in the appropriate relationship.
--
-- (2) is @[y,z] -> [x]@, used to perform the calculation in a
-- forward direction.
--
-- (3) is @[x,z] -> [y]@, used to run the calculation backwards to
-- determine the first input argument
--
-- (4) is @[x,y] -> [z]@, used to run the calculation backwards to
-- determine the second input argument
--
-- [@lbs@] is a list of specific label values for which a variable binding
-- modifier will be generated.
--
-- Note: an irrefutable pattern match for @lbs@ is used so that a name
-- for the 'VarBindingModify' value can be extracted using an undefined
-- label value.
--
makeVmod21inv :: (Ord lb, Ord vn) => ApplyModifier lb vn
makeVmod21inv nam [f0,f1,f2,f3] lbs@(~[lb1,lb2,lb3]) = VarBindingModify
{ vbmName = nam
, vbmApply = concatMap app1
, vbmVocab = lbs
, vbmUsage = [[],[lb1],[lb2],[lb3]]
}
where
app1 vbind = app2 (map (vbMap vbind) lbs) vbind
app2 [Just v1,Just v2,Just v3] vbind = selv (f0 [v1,v2,v3]) vbind
app2 [Nothing,Just v2,Just v3] vbind = addv lb1 (f1 [v2,v3]) vbind
app2 [Just v1,Nothing,Just v3] vbind = addv lb2 (f2 [v1,v3]) vbind
app2 [Just v1,Just v2,Nothing] vbind = addv lb3 (f3 [v1,v2]) vbind
app2 _ _ = []
makeVmod21inv _ _ _ =
error "makeVmod21inv: requires 4 functions and 3 labels"
-- |'ApplyModifier' function for use with 'DatatypeMod' in cases
-- when the value mapping is a @2->1@ non-invertable function, such as
-- logical @AND@ or @OR@.
--
-- [@nam@] is the name from the 'DatatypeMod' value that is carried into
-- the resulting variable binding modifier.
--
-- [@fns@] are functions used to implement details of the variable
-- binding modifier:
--
-- (1) is @[x,y,z] -> [?]@, used as a filter (i.e. not creating any
-- new variable bindings), returning a non-empty list if
-- @x@, @y@ and @z@ are in the appropriate relationship.
--
-- (2) is @[y,z] -> [x]@, used to perform the calculation in a
-- forward direction.
--
-- [@lbs@] is a list of specific label values for which a variable binding
-- modifier will be generated.
--
-- Note: an irrefutable pattern match for @lbs@ is used so that a name
-- for the 'VarBindingModify' value can be extracted using an undefined
-- label value.
--
makeVmod21 :: (Ord lb, Ord vn) => ApplyModifier lb vn
makeVmod21 nam [f0,f1] lbs@(~[lb1,_,_]) = VarBindingModify
{ vbmName = nam
, vbmApply = concatMap app1
, vbmVocab = lbs
, vbmUsage = [[],[lb1]]
}
where
app1 vbind = app2 (map (vbMap vbind) lbs) vbind
app2 [Just v1,Just v2,Just v3] vbind = selv (f0 [v1,v2,v3]) vbind
app2 [Nothing,Just v2,Just v3] vbind = addv lb1 (f1 [v2,v3]) vbind
app2 _ _ = []
makeVmod21 _ _ _ =
error "makeVmod21: requires 2 functions and 3 labels"
-- |'ApplyModifier' function for use with 'DatatypeMod' in cases
-- when the value mapping is a simple comparson of two values.
--
-- [@nam@] is the name from the 'DatatypeMod' value that is carried into
-- the resulting variable binding modifier.
--
-- [@fns@] are functions used to implement details of the variable
-- binding modifier:
--
-- (1) is @[x,y] -> [?]@, used as a filter (i.e. not creating any
-- new variable bindings), returning a non-empty list if
-- @x@ and @y@ are in the appropriate relationship.
--
-- [@lbs@] is a list of specific label values for which a variable binding
-- modifier will be generated.
--
-- Note: an irrefutable pattern match for @lbs@ is used so that a name
-- for the 'VarBindingModify' value can be extracted using an undefined
-- label value.
--
makeVmod20 :: (Eq lb, Show lb, Eq vn, Show vn) => ApplyModifier lb vn
makeVmod20 nam [f0] lbs@(~[_,_]) = VarBindingModify
{ vbmName = nam
, vbmApply = concatMap app1
, vbmVocab = lbs
, vbmUsage = [[]]
}
where
app1 vbind = app2 (map (vbMap vbind) lbs) vbind
app2 [Just v1,Just v2] vbind = selv (f0 [v1,v2]) vbind
app2 _ _ = []
makeVmod20 _ _ _ =
error "makeVmod20: requires 1 function and 2 labels"
-- |'ApplyModifier' function for use with 'DatatypeMod' in cases
-- when the value mapping is a @2->2@ non-invertable function, such as
-- quotient/remainder
--
-- [@nam@] is the name from the 'DatatypeMod' value that is carried into
-- the resulting variable binding modifier.
--
-- [@fns@] are functions used to implement details of the variable
-- binding modifier:
--
-- (1) is @[w,x,y,z] -> [?]@, used as a filter (i.e. not creating
-- any new variable bindings), returning a non-empty list if
-- @w@, @x@, @y@ and @z@ are in the appropriate relationship.
--
-- (2) is @[y,z] -> [w,x]@, used to perform the calculation given
-- two input values.
--
-- [@lbs@] is a list of specific label values for which a variable binding
-- modifier will be generated.
--
-- Note: an irrefutable pattern match for @lbs@ is used so that a name
-- for the 'VarBindingModify' value can be extracted using an undefined
-- label value.
--
-- NOTE: this might be generalized to allow one of @w@ or @x@ to be
-- specified, and return null if it doesn't match the calculated value.
--
makeVmod22 :: (Ord lb, Ord vn) => ApplyModifier lb vn
makeVmod22 nam [f0,f1] lbs@(~[lb1,lb2,_,_]) = VarBindingModify
{ vbmName = nam
, vbmApply = concatMap app1
, vbmVocab = lbs
, vbmUsage = [[],[lb1,lb2]]
}
where
app1 vbind = app2 (map (vbMap vbind) lbs) vbind
app2 [Just v1,Just v2,Just v3,Just v4] vbind =
selv (f0 [v1,v2,v3,v4]) vbind
app2 [Nothing,Nothing,Just v3,Just v4] vbind =
addv2 lb1 lb2 (f1 [v3,v4]) vbind
app2 _ _ = []
makeVmod22 _ _ _ =
error "makeVmod22: requires 2 functions and 4 labels"
-- |'ApplyModifier' function for use with 'DatatypeMod' in cases
-- when the value mapping is a @N->1@ function,
-- such as Sigma (sum) of a vector.
--
-- [@nam@] is the name from the 'DatatypeMod' value that is carried into
-- the resulting variable binding modifier.
--
-- [@fns@] are functions used to implement details of the variable
-- binding modifier:
--
-- (1) is @[x,y...] -> [?]@, used as a filter (i.e. not creating
-- any new variable bindings), returning a non-empty list if
-- @x@ and @y...@ are in the appropriate relationship.
--
-- (2) is @[y...] -> [x]@, used to perform the calculation.
--
-- [@lbs@] is a list of specific label values for which a variable binding
-- modifier will be generated.
--
-- Note: an irrefutable pattern match for @lbs@ is used so that a name
-- for the 'VarBindingModify' value can be extracted using an undefined
-- label value.
--
makeVmodN1 :: (Ord lb, Ord vn) => ApplyModifier lb vn
makeVmodN1 nam [f0,f1] lbs@(~(lb1:_)) = VarBindingModify
{ vbmName = nam
, vbmApply = concatMap app1
, vbmVocab = lbs
, vbmUsage = [[],[lb1]]
}
where
app1 vbind = app2 (map (vbMap vbind) lbs) vbind
app2 vs@(v1:_) vbind
| isJust v1 && isJustvs = selv (f0 jvs) vbind
| isJustvs = addv lb1 (f1 jvs) vbind
| otherwise = []
where
isJustvs = all isJust vs
jvs = catMaybes vs
app2 _ _ = error "app2 sent empty list" -- -Wall
makeVmodN1 _ _ _ =
error "makeVmodN1: requires 2 functions and at 1 or more labels"
--------------------------------------------------------
-- Local helper functions for makeVmodXXX variants
--------------------------------------------------------
-- Add value to variable variable binding, if value is singleton list,
-- otherwise return empty list.
addv :: (Ord lb, Ord vt)
=> lb -> [vt] -> VarBinding lb vt
-> [VarBinding lb vt]
addv lb [val] vbind = [addVarBinding lb val vbind]
addv _ _ _ = []
-- Add two entries to variable variable binding, if value supplied is
-- a doubleton list, otherwise return empty list.
addv2 :: (Ord lb, Ord vt)
=> lb -> lb -> [vt] -> VarBinding lb vt
-> [VarBinding lb vt]
addv2 lb1 lb2 [val1,val2] vbind = [addVarBinding lb1 val1 $
addVarBinding lb2 val2 vbind]
addv2 _ _ _ _ = []
-- If supplied value is non-empty list return supplied variable binding,
-- otherwise return empty list.
selv :: [vt] -> varBinding lb vt -> [varBinding lb vt]
selv [] _ = []
selv _ vbind = [vbind]
--------------------------------------------------------------
-- Functions for evaluating arguments in a datatype relation
--------------------------------------------------------------
--
-- altArgs is a generic function for evaluating datatype relation
-- values, based on suppied functions and argument values
--
-- UnaryFnDescr, UnaryFnApply and unaryFnApp:
-- are support types and function for using altArgs to
-- evaluate relations on unary functions (binary relations).
--
-- BinaryFnDescr, BinaryFnApply and binaryFnApp:
-- are support types and function for using altArgs to
-- evaluate relations on binary functions (3-way relations).
--
-- ListFnDescr, ListFnApply and listFnApp:
-- are support types and function for using altArgs to
-- evaluate relations on list functions (n-way relations),
-- where the first member of the list is the value of a
-- fold of a function over the rest of the list.
--
-- See experimental module spike-altargs.hs for test cases and
-- development steps for this function.
-- |Given a list of argument values and a list of functions for
-- calculating new values from supplied values, return a list
-- of argument values, or @Nothing@ if the supplied values are
-- inconsistent with the calculations specified.
--
-- Each list of values returned corresponds to a set of values that
-- satisfy the relation, consistent with the values supplied.
--
-- Functions are described as tuple consisting of:
--
-- (a) a predicate that the argument is required to satisfy
--
-- (b) a function to apply,
--
-- (c) a function to apply function (b) to a list of arguments
--
-- (d) argument list index values to which the function is applied.
--
-- Each supplied argument is of the form @Maybe a@, where the argument
-- has value type a. @Nothing@ indicates arguments of unknown value.
--
-- The basic idea is that, for each argument position in the relation,
-- a function may be supplied to calculate that argument's possible values
-- from some combination of the other arguments. The results calculated
-- in this way are compared with the original arguments provided:
-- if the values conflict then the relation is presumed to be
-- unsatisfiable with the supplied values, and @Nothing@ is returned;
-- if there are any calculated values for arguments supplied without
-- any values, then tbe calculated values are used.
-- If there are any arguments for which no values are supplied or
-- calculated, then the relation is presumed to be underdetermined,
-- and @Just []@ is returned.
--
altArgs ::
(Eq vt)
=> DatatypeRelPr vt
-> [(vt->Bool,[b])]
-- ^ a list of argument value predicates and
-- function descriptors. The predicate indicates any
-- additional constraints on argument values (e.g. the result
-- of abs must be positive). Use @(const True)@ for the predicate
-- associated with unconstrained relation arguments.
-- For each argument, a list of function descriptors is
-- supplied corresponding to alternative values (e.g. a square
-- relation would offer two alternative values for the root.)
-> ((vt->Bool)->b->[Maybe vt]->Maybe [vt])
-- ^ a function that takes an argument value predicate,
-- a function descriptor and applies it to a supplied argument
-- list to return:
-- @Just a@ calculated list of one or more possible argument values,
-- @Just []@ indicating insufficient information provided, or
-- @Nothing@ indicating inconsistent information provided.
-- May be one of 'unaryFnApp', 'binaryFnApp', 'listFnApp' or
-- some other caller-supplied value.
-> DatatypeRelFn vt
-- ^ The return value can be used as the
-- 'dtRelFunc' component of a 'DatatypeRel' value.
altArgs pr fnss apfn args = cvals4 cvals3
where
-- Calculate new value(s) for each argument from supplied values, and
-- lift inconsistency indicator (Just/Nothing) to outermost Monad.
-- cvals1 :: [Maybe [vt]]
cvals1 = flist (map (applyFdescToTuple apfn) fnss) args
-- Merge calculated values with supplied arguments, and again
-- lift inconsistency indicator (Just/Nothing) to outermost Monad.
-- cvals2 :: Maybe [[vt]]
cvals2 = sequence $ mergeTupleVals (map fst fnss) args cvals1
-- Map list of alternative values for each tuple member to
-- a list of alternative tuples.
cvals3 = fmap sequence cvals2
-- Check each tuple against the supplied predicate.
-- If any of the alternative tuples does not match the predicate
-- then signal an inconsistency.
cvals4 Nothing = Nothing
cvals4 cvs@(Just ts) = if all pr ts then cvs else Nothing
-- Perform alternative calculations for single result value
-- Each result value is a list of zero or more alternatives
-- that can be calculated from available parameters, or
-- Nothing if the available parameters are inconsistent.
--
-- apfn is the function that actually applies an element of
-- the function descriptor to a tuple of Maybe arguments
-- (where Nothing is used to indicate an unknown value)
-- (p,fns) is a pair consisting of a value-checking predicate
-- for the corresponding tuple member, and a list of
-- function descriptors that each return one or more
-- values the tuple member, calculated from other values
-- that are present. Just [] means no values are
-- calculated for this member, and Nothing means the
-- calculation has detected tuple values supplied that
-- are inconsistent with the datatype relation concerned.
-- args is a tuple of Maybe tuple elements, (where Nothing
-- indicates an unknown value).
--
-- Returns Maybe a list of alternative values for the member,
-- Just [] to indicate insufficient information to calculate
-- any new values, and Nothing to indicate an inconsistency.
--
applyFdescToTuple ::
((vt->Bool)->b->[Maybe vt]->Maybe [vt]) -> (vt->Bool,[b]) -> [Maybe vt]
-> Maybe [vt]
applyFdescToTuple apfn (p,fns) args =
concat <$> sequence cvals
where
-- cvals :: [Maybe [vt]]
cvals = flist (map (apfn p) fns) args
-- Merge calculated tuple values with supplied tuple, checking for consistency.
--
-- ps predicates used for isolated validation of each tuple member
-- args supplied tuple values, with Nothing for unknown values
-- cvals list of alternative calculated values for each tuple member,
-- or Nothing if an inconsistency has been detected by the
-- tuple-calculation functions. Note that this list may contain
-- more entries than args; the surplus entries are ignored
-- (see list functions for how this is used).
--
-- Returns a tuple of Maybe lists of values for each tuple member,
-- containing Nothing if an inconsistency has been detected in the
-- supplied values.
--
mergeTupleVals :: (Eq a) => [a->Bool] -> [Maybe a] -> [Maybe [a]] -> [Maybe [a]]
mergeTupleVals _ _ (Nothing:_) = [Nothing]
mergeTupleVals (_:ps) (Nothing:a1s) (Just a2s:a2ss)
= Just a2s:mergeTupleVals ps a1s a2ss
mergeTupleVals (p:ps) (Just a1:a1s) (Just []:a2ss)
| p a1 = Just [a1]:mergeTupleVals ps a1s a2ss
| otherwise = [Nothing]
mergeTupleVals (p:ps) (Just a1:a1s) (Just a2s:a2ss)
| p a1 && elem a1 a2s = Just [a1]:mergeTupleVals ps a1s a2ss
| otherwise = [Nothing]
mergeTupleVals _ [] _ = []
mergeTupleVals _ _ _ = [Nothing]
-- |'altArgs' support for unary functions: function descriptor type
type UnaryFnDescr a = (a->a,Int)
-- |'altArgs' support for unary functions: function descriptor table type
type UnaryFnTable a = [(a->Bool,[UnaryFnDescr a])]
-- |'altArgs' support for unary functions: function applicator type
type UnaryFnApply a = (a->Bool) -> UnaryFnDescr a -> [Maybe a] -> Maybe [a]
-- |'altArgs' support for unary functions: function applicator
unaryFnApp :: UnaryFnApply a
unaryFnApp p (f1,n) args = apf (args !! n)
where
apf (Just a) = if p r then Just [r] else Nothing where r = f1 a
apf Nothing = Just []
-- |'altArgs' support for binary functions: function descriptor type
type BinaryFnDescr a = (a -> a -> a, Int, Int)
-- |'altArgs' support for binary functions: function descriptor table type
type BinaryFnTable a = [(a -> Bool, [BinaryFnDescr a])]
-- |'altArgs' support for binary functions: function applicator type
type BinaryFnApply a =
(a -> Bool) -> BinaryFnDescr a -> [Maybe a] -> Maybe [a]
-- |'altArgs' support for binary functions: function applicator
binaryFnApp :: BinaryFnApply a
binaryFnApp p (f,n1,n2) args = apf (args !! n1) (args !! n2)
where
apf (Just a1) (Just a2) = if p r then Just [r] else Nothing
where r = f a1 a2
apf _ _ = Just []
-- |'altArgs' support for binary function with provision for indicating
-- inconsistent supplied values: function descriptor type
type BinMaybeFnDescr a = (a -> a ->Maybe [a], Int, Int)
-- |'altArgs' support for binary function with provision for indicating
-- inconsistent supplied values: function descriptor table type
type BinMaybeFnTable a = [(a -> Bool, [BinMaybeFnDescr a])]
-- |'altArgs' support for binary function with provision for indicating
-- inconsistent supplied values: function applicator type
type BinMaybeFnApply a =
(a -> Bool) -> BinMaybeFnDescr a -> [Maybe a] -> Maybe [a]
-- |'altArgs' support for binary function with provision for indicating
-- inconsistent supplied values: function applicator
binMaybeFnApp :: BinMaybeFnApply a
binMaybeFnApp p (f,n1,n2) args = apf (args !! n1) (args !! n2)
where
apf (Just a1) (Just a2) = if pm r then r else Nothing
where
r = f a1 a2
pm Nothing = False
pm (Just x) = all p x
apf _ _ = Just []
-- |'altArgs' support for list functions (e.g. sum over list of args),
-- where first element of list is a fold over the rest of the list,
-- and remaining elements of list can be calculated in terms
-- of the result of the fold and the remaining elements
--
-- List function descriptor is
--
-- (a) list-fold function, f (e.g. (+)
--
-- (b) list-fold identity, z (e.g. 0)
--
-- (c) list-fold-function inverse, g (e.g. (-))
--
-- (d) index of element to evaluate
--
-- such that:
--
-- > (a `f` z) == (z `f` a) == a
-- > (a `g` c) == b <=> a == b `f` c
-- > (a `g` z) == a
-- > (a `g` a) == z
--
-- and the result of the folded function does not depend on
-- the order that the list elements are processed.
--
-- NOTE: the list of 'ListFnDescr' values supplied to 'altArgs' must
-- be at least as long as the argument list. In many cases, Haskell
-- lazy evaluation can be used to supply an arbitrarily long list.
-- See test cases in spike-altargs.hs for an example.
--
-- Function descriptor type
type ListFnDescr a = (a -> a -> a, a, a -> a -> a, Int)
-- |Function table type
type ListFnTable a = [(a -> Bool, [ListFnDescr a])]
-- |'altArgs' support for list functions: function applicator type
type ListFnApply a = (a -> Bool) -> ListFnDescr a -> [Maybe a] -> Maybe [a]
-- |'altArgs' support for list functions: function applicator
listFnApp :: ListFnApply a
listFnApp p (f,z,g,n) (a0:args)
| n == 0 =
app $ foldr (apf f) (Just [z]) args
| otherwise =
app $ apf g a0 (foldr (apf f) (Just [z]) (args `deleteIndex` (n - 1)))
where
apf :: (a->a->a) -> Maybe a -> Maybe [a] -> Maybe [a]
apf fn (Just a1) (Just [a2]) = Just [fn a1 a2]
apf _ _ _ = Just []
-- app :: Maybe [a] -> Maybe [a]
app Nothing = Nothing
app r@(Just [a]) = if p a then r else Nothing
app _ = Just []
listFnApp _ _ [] = error "listFnApp called with an empty list" -- -Wall
-- |Delete the n'th element of a list, returning the result
--
-- If the list doesn't have an n'th element, return the list unchanged.
--
deleteIndex :: [a] -> Int -> [a]
deleteIndex [] _ = []
deleteIndex xxs@(x:xs) n
| n < 0 = xxs
| n == 0 = xs
| otherwise = x:deleteIndex xs (n-1)
{-
testdi1 = deleteIndex [1,2,3,4] 0 == [2,3,4]
testdi2 = deleteIndex [1,2,3,4] 1 == [1,3,4]
testdi3 = deleteIndex [1,2,3,4] 2 == [1,2,4]
testdi4 = deleteIndex [1,2,3,4] 3 == [1,2,3]
testdi5 = deleteIndex [1,2,3,4] 4 == [1,2,3,4]
testdi6 = deleteIndex [1,2,3,4] (-1) == [1,2,3,4]
testdi = and
[ testdi1, testdi2, testdi3, testdi4, testdi5, testdi6 ]
-}
--------------------------------------------------------
-- Datatype sub/supertype description
--------------------------------------------------------
-- |Describe a subtype/supertype relationship between a pair of datatypes.
--
-- Originally, I had this as a supertype field of the DatatypeVal structure,
-- but that suffered from some problems:
--
-- * supertypes may be introduced retrospectively,
--
-- * the relationship expressed with respect to a single datatype
-- cannot indicate how to do injections/restrictions between the
-- underlying value types.
--
-- [@ex@] is the type of expression with which the datatype may be used.
--
-- [@lb@] is the type of the variable labels used.
--
-- [@vn@] is the type of value node used to contain a datatyped value
--
-- [@supvt@] is the internal value type of the super-datatype
--
-- [@subvt@] is the internal value type of the sub-datatype
--
data DatatypeSub ex lb vn supvt subvt = DatatypeSub
{ trelSup :: DatatypeVal ex supvt lb vn
-- ^ Datatype that is a supertype of @trelSub@,
-- having value space @supvt@.
, trelSub :: DatatypeVal ex subvt lb vn
-- ^ Datatype that is a subtype of @trelSup@,
-- having value space @supvt@.
, trelToSup :: subvt -> supvt
-- ^ Function that maps subtype value to
-- corresponding supertype value.
, trelToSub :: supvt -> Maybe subvt
-- ^ Function that maps supertype value to
-- corresponding subtype value, if there
-- is such a value.
}
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012, 2018, 2019, 2022 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/GraphClass.hs 0000644 0000000 0000000 00000017264 14220136201 015525 0 ustar 00 0000000 0000000 {-# LANGUAGE CPP #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE DeriveTraversable #-}
#if (__GLASGOW_HASKELL__ >= 802)
{-# LANGUAGE DerivingStrategies #-}
#endif
--------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : GraphClass
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012, 2016, 2020, 2022 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : CPP, DeriveTraversable, DerivingStrategies, MultiParamTypeClasses
--
-- This module defines a Labelled Directed Graph and Label classes,
-- and the Arc datatype.
--
--------------------------------------------------------------------------------
------------------------------------------------------------
-- Define LDGraph, arc and related classes and types
------------------------------------------------------------
module Swish.GraphClass
( LDGraph(..)
, Label(..)
, Arc(..)
, ArcSet
, Selector
, arc, arcToTriple, arcFromTriple
, hasLabel, arcLabels -- , arcNodes
, getComponents
)
where
import Data.Hashable (Hashable(..))
import Data.List (foldl')
import Data.Ord (comparing)
import qualified Data.Foldable as F
import qualified Data.Set as S
import qualified Data.Traversable as T
-- NOTE: I wanted to declare this as a subclass of Functor, but
-- the constraint on the label type seems to prevent that.
-- So I've just declared specific instances to be Functors.
--
{-|
Labelled Directed Graph class.
Minimum required implementation:
'emptyGraph', 'setArcs', and 'getArcs'.
-}
class LDGraph lg lb where
-- | Create the empty graph.
emptyGraph :: lg lb
-- | Replace the existing arcs in the graph.
setArcs :: lg lb -> ArcSet lb -> lg lb
-- | Extract all the arcs from a graph
getArcs :: lg lb -> ArcSet lb
-- | Extract those arcs that match the given `Selector`.
extract :: (Ord lb) => Selector lb -> lg lb -> lg lb
extract sel = update (S.filter sel)
-- | Add the two graphs
addGraphs :: (Ord lb) => lg lb -> lg lb -> lg lb
addGraphs addg = update (S.union (getArcs addg))
-- | Remove those arcs in the first graph from the second
-- graph
delete ::
(Ord lb) =>
lg lb -- ^ g1
-> lg lb -- ^ g2
-> lg lb -- ^ g2 - g1 -> g3
delete g1 g2 = setArcs g2 (getArcs g2 `S.difference` getArcs g1)
-- | Enumerate the distinct labels contained in a graph;
-- that is, any label that appears in the subject,
-- predicate or object position of an `Arc`.
labels :: (Ord lb) => lg lb -> S.Set lb
labels = getComponents arcLabels . getArcs
-- | Enumerate the distinct nodes contained in a graph;
-- that is, any label that appears in the subject
-- or object position of an `Arc`.
nodes :: (Ord lb) => lg lb -> S.Set lb
nodes = getComponents arcNodes . getArcs
-- | Update the arcs in a graph using a supplied function.
update :: (ArcSet lb -> ArcSet lb) -> lg lb -> lg lb
update f g = setArcs g ( f (getArcs g) )
-- | Extract components from a set.
getComponents :: Ord b => (a -> [b]) -> S.Set a -> S.Set b
getComponents f =
let ins sgr = foldl' (flip S.insert) sgr . f
in S.foldl' ins S.empty
-- | Label class.
--
-- A label may have a fixed binding, which means that the label identifies (is) a
-- particular graph node, and different such labels are always distinct nodes.
-- Alternatively, a label may be unbound (variable), which means that it is a
-- placeholder for an unknown node label. Unbound node labels are used as
-- graph-local identifiers for indicating when the same node appears in
-- several arcs.
--
-- For the purposes of graph-isomorphism testing, fixed labels are matched when they
-- are the same. Variable labels may be matched with any other variable label.
-- Our definition of isomorphism (for RDF graphs) does not match variable labels
-- with fixed labels.
--
-- We do not need Ord/Show constraints here, but it means we can just use
-- Label as a short-form for Ord/Show in code
class (Ord lb, Show lb) => Label lb where
-- | Does this node have a variable binding?
labelIsVar :: lb -> Bool
-- | Calculate the hash of the label using the supplied seed.
labelHash :: Int -> lb -> Int
-- could provide a default of
-- labelHash = hashWithSalt
-- but this would then force a Hashable constraint
-- | Extract the local id from a variable node.
getLocal :: lb -> String
-- | Make a label value from a local id.
makeLabel :: String -> lb
-- | Arc type.
--
-- Prior to @0.7.0.0@ you could also use @asubj@, @apred@ and @aobj@
-- to access the elements of the arc.
--
data Arc lb = Arc
{ arcSubj :: lb -- ^ The subject of the arc.
, arcPred :: lb -- ^ The predicate (property) of the arc.
, arcObj :: lb -- ^ The object of the arc.
}
deriving
#if (__GLASGOW_HASKELL__ >= 802)
stock
#endif
(Eq, Functor, F.Foldable, T.Traversable)
-- | A set - or graph - of arcs.
type ArcSet lb = S.Set (Arc lb)
instance (Hashable lb) => Hashable (Arc lb) where
#if MIN_VERSION_hashable(1,2,0)
#else
hash (Arc s p o) = hash s `hashWithSalt` p `hashWithSalt` o
#endif
hashWithSalt salt (Arc s p o) = salt `hashWithSalt` s `hashWithSalt` p `hashWithSalt` o
-- | Create an arc.
arc :: lb -- ^ The subject of the arc.
-> lb -- ^ The predicate of the arc.
-> lb -- ^ The object of the arc.
-> Arc lb
arc = Arc
-- | Convert an Arc into a tuple.
arcToTriple :: Arc lb -> (lb,lb,lb)
arcToTriple (Arc s p o) = (s, p, o)
-- | Create an Arc from a tuple.
arcFromTriple :: (lb,lb,lb) -> Arc lb
arcFromTriple (s,p,o) = Arc s p o
instance Ord lb => Ord (Arc lb) where
compare = comparing arcToTriple
instance (Show lb) => Show (Arc lb) where
show (Arc lb1 lb2 lb3) =
"(" ++ show lb1 ++ "," ++ show lb2 ++ "," ++ show lb3 ++ ")"
-- | Identify arcs.
type Selector lb = Arc lb -> Bool
-- | Does the arc contain the label in any position (subject, predicate, or object)?
hasLabel :: (Eq lb) => lb -> Arc lb -> Bool
hasLabel lbv lb = lbv `elem` arcLabels lb
-- | Return all the labels in an arc.
arcLabels :: Arc lb -> [lb]
arcLabels (Arc lb1 lb2 lb3) = [lb1,lb2,lb3]
-- | Return just the subject and object labels in the arc.
arcNodes :: Arc lb -> [lb]
arcNodes (Arc lb1 _ lb3) = [lb1,lb3]
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012, 2016, 2020, 2022 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/GraphMatch.hs 0000644 0000000 0000000 00000064426 14220136201 015516 0 ustar 00 0000000 0000000 {-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
--------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : GraphMatch
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012, 2016, 2018, 2020, 2022 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : FlexibleInstances, MultiParamTypeClasses
--
-- This module contains graph-matching logic.
--
-- The algorithm used is derived from a paper on RDF graph matching
-- by Jeremy Carroll .
--
--------------------------------------------------------------------------------
module Swish.GraphMatch
( graphMatch,
-- * Exported for testing
LabelMap, GenLabelMap(..), LabelEntry, GenLabelEntry(..),
ScopedLabel(..), makeScopedLabel, makeScopedArc,
LabelIndex, EquivalenceClass, nullLabelVal, emptyMap,
labelIsVar, labelHash,
mapLabelIndex, setLabelHash, newLabelMap,
graphLabels, assignLabelMap, newGenerationMap,
graphMatch1, graphMatch2, equivalenceClasses, reclassify
) where
import Swish.GraphClass (Arc(..), ArcSet, Label(..))
import Swish.GraphClass (getComponents, arcLabels, hasLabel, arcToTriple)
import Control.Exception.Base (assert)
import Control.Arrow (second)
import Data.Function (on)
import Data.Hashable (hashWithSalt)
import Data.List (foldl', sortBy, groupBy, partition)
import Data.Ord (comparing)
import Data.Word
import qualified Data.List as L
import qualified Data.Map as M
import qualified Data.Set as S
--------------------------
-- Label index value type
--------------------------
--
-- | LabelIndex is a unique value assigned to each label, such that
-- labels with different values are definitely different values
-- in the graph; e.g. do not map to each other in the graph
-- bijection. The first member is a generation counter that
-- ensures new values are distinct from earlier passes.
type LabelIndex = (Word32, Word32)
-- | The null, or empty, index value.
nullLabelVal :: LabelIndex
nullLabelVal = (0, 0)
-----------------------
-- Label mapping types
-----------------------
-- | A Mapping between a label and a value (e.g. an index
-- value).
data (Label lb) => GenLabelEntry lb lv = LabelEntry lb lv
-- | A label associated with a 'LabelIndex'
type LabelEntry lb = GenLabelEntry lb LabelIndex
instance (Label lb, Show lv) => Show (GenLabelEntry lb lv) where
show (LabelEntry k v) = show k ++ ":" ++ show v
instance (Label lb, Eq lv) => Eq (GenLabelEntry lb lv) where
(LabelEntry k1 v1) == (LabelEntry k2 v2) = (k1,v1) == (k2,v2)
instance (Label lb, Ord lv) => Ord (GenLabelEntry lb lv) where
(LabelEntry lb1 lv1) `compare` (LabelEntry lb2 lv2) =
(lb1, lv1) `compare` (lb2, lv2)
-- | Type for label->index lookup table
data (Label lb, Eq lv, Show lv) => GenLabelMap lb lv =
LabelMap Word32 (M.Map lb lv)
-- | A label lookup table specialized to 'LabelIndex' indices.
type LabelMap lb = GenLabelMap lb LabelIndex
instance (Label lb) => Show (LabelMap lb) where
show = showLabelMap
instance (Label lb) => Eq (LabelMap lb) where
LabelMap gen1 lmap1 == LabelMap gen2 lmap2 =
(gen1, lmap1) == (gen2, lmap2)
-- | The empty label map table.
emptyMap :: (Label lb) => LabelMap lb
emptyMap = LabelMap 1 M.empty
--------------------------
-- Equivalence class type
--------------------------
--
-- | Type for equivalence class description
-- (An equivalence class is a collection of labels with
-- the same 'LabelIndex' value.)
type EquivalenceClass lb = (LabelIndex, [lb])
{-
ecIndex :: EquivalenceClass lb -> LabelIndex
ecIndex = fst
-}
ecLabels :: EquivalenceClass lb -> [lb]
ecLabels = snd
{-
ecSize :: EquivalenceClass lb -> Int
ecSize = length . ecLabels
-}
ecRemoveLabel :: (Label lb) => EquivalenceClass lb -> lb -> EquivalenceClass lb
ecRemoveLabel xs l = second (L.delete l) xs
------------------------------------------------------------
-- Filter, ungroup, sort and group pairs by first member
------------------------------------------------------------
{-
pairSelect :: ((a,b) -> Bool) -> ((a,b) -> c) -> [(a,b)] -> [c]
pairSelect p f as = map f (filter p as)
-}
-- | Ungroup the pairs.
pairUngroup ::
(a,[b]) -- ^ Given (a,bs)
-> [(a,b)] -- ^ Returns (a,b) for all b in bs
pairUngroup (a,bs) = [ (a,b) | b <- bs ]
-- | Order the pairs based on the first argument.
pairSort :: (Ord a) => [(a,b)] -> [(a,b)]
pairSort = sortBy (comparing fst)
-- TODO: use set on input
-- | Group the pairs based on the first argument.
pairGroup :: (Ord a) => [(a,b)] -> [(a,[b])]
pairGroup = map (factor . unzip) . groupBy eqFirst . pairSort
where
-- as is not [] by construction, but would be nice to have
-- this enforced by the types
factor (as, bs) = (head as, bs)
eqFirst = (==) `on` fst
------------------------------------------------------------
-- Augmented graph label value - for graph matching
------------------------------------------------------------
--
-- | This instance of class label adds a graph identifier to
-- each variable label, so that variable labels from
-- different graphs are always seen as distinct values.
--
-- The essential logic added by this class instance is embodied
-- in the eq and hash functions. Note that variable label hashes
-- depend only on the graph in which they appear, and non-variable
-- label hashes depend only on the variable. Label hash values are
-- used when initializing a label equivalence-class map (and, for
-- non-variable labels, also for resolving hash collisions).
data (Label lb) => ScopedLabel lb = ScopedLabel Int lb
-- | Create a scoped label given an identifier and label.
makeScopedLabel :: (Label lb) => Int -> lb -> ScopedLabel lb
makeScopedLabel = ScopedLabel
-- | Create an arc containining a scoped label with the given identifier.
makeScopedArc :: (Label lb) => Int -> Arc lb -> Arc (ScopedLabel lb)
makeScopedArc scope = fmap (ScopedLabel scope)
instance (Label lb) => Label (ScopedLabel lb) where
getLocal lab = error $ "getLocal for ScopedLabel: " ++ show lab
makeLabel locnam = error $ "makeLabel for ScopedLabel: " ++ locnam
labelIsVar (ScopedLabel _ lab) = labelIsVar lab
labelHash seed (ScopedLabel scope lab)
| labelIsVar lab = seed `hashWithSalt` scope
| otherwise = labelHash seed lab
instance (Label lb) => Eq (ScopedLabel lb) where
(ScopedLabel s1 l1) == (ScopedLabel s2 l2)
= l1 == l2 && s1 == s2
instance (Label lb) => Show (ScopedLabel lb) where
show (ScopedLabel s1 l1) = show s1 ++ ":" ++ show l1
instance (Label lb) => Ord (ScopedLabel lb) where
compare (ScopedLabel s1 l1) (ScopedLabel s2 l2) =
case compare s1 s2 of
LT -> LT
EQ -> compare l1 l2
GT -> GT
-- QUS: why doesn't this return Maybe (LabelMap (ScopedLabel lb)) ?
-- TODO: Should this use Set (Arc lb) instead of [Arc lb]?
-- | Graph matching function accepting two lists of arcs and
-- returning a node map if successful
--
graphMatch :: (Label lb) =>
(lb -> lb -> Bool)
-- ^ a function that tests for additional constraints
-- that may prevent the matching of a supplied pair
-- of nodes. Returns `True` if the supplied nodes may be
-- matched. (Used in RDF graph matching for checking
-- that formula assignments are compatible.)
-> ArcSet lb -- ^ the first graph to be compared
-> ArcSet lb -- ^ the second graph to be compared
-> (Bool, LabelMap (ScopedLabel lb))
-- ^ If the first element is `True` then the second element maps each label
-- to an equivalence class identifier, otherwise it is just
-- `emptyMap`.
--
graphMatch matchable gs1 gs2 =
let
sgs1 = {- trace "sgs1 " $ -} S.map (makeScopedArc 1) gs1
sgs2 = {- trace "sgs2 " $ -} S.map (makeScopedArc 2) gs2
ls1 = {- traceShow "ls1 " $ -} graphLabels sgs1
ls2 = {- traceShow "ls2 " $ -} graphLabels sgs2
lmap = {- traceShow "lmap " $ -}
newGenerationMap $
assignLabelMap ls1 $
assignLabelMap ls2 emptyMap
ec1 = {- traceShow "ec1 " $ -} equivalenceClasses lmap ls1
ec2 = {- traceShow "ec2 " $ -} equivalenceClasses lmap ls2
ecpairs = zip (pairSort ec1) (pairSort ec2)
matchableScoped (ScopedLabel _ l1) (ScopedLabel _ l2) = matchable l1 l2
match = graphMatch1 False matchableScoped sgs1 sgs2 lmap ecpairs
in
if length ec1 /= length ec2 then (False,emptyMap) else match
-- TODO:
--
-- * replace Equivalence class pair by @(index,[lb],[lb])@ ?
--
-- * possible optimization: the @graphMapEq@ test should be
-- needed only if `graphMatch2` has been used to guess a
-- mapping; either:
-- a) supply flag saying guess has been used, or
-- b) move test to `graphMatch2` and use different
-- test to prevent rechecking for each guess used.
--
-- | Recursive graph matching function
--
-- This function assumes that no variable label appears in both graphs.
-- (Function `graphMatch`, which calls this, ensures that all variable
-- labels are distinct.)
--
graphMatch1 ::
(Label lb)
=> Bool
-- ^ `True` if a guess has been used before trying this comparison,
-- `False` if nodes are being matched without any guesswork
-> (lb -> lb -> Bool)
-- ^ Test for additional constraints that may prevent the matching
-- of a supplied pair of nodes. Returns `True` if the supplied
-- nodes may be matched.
-> ArcSet lb
-- ^ (@gs1@ argument)
-- first of two lists of arcs (triples) to be compared
-> ArcSet lb
-- ^ (@gs2@ argument)
-- secind of two lists of arcs (triples) to be compared
-> LabelMap lb
-- ^ the map so far used to map label values to equivalence class
-- values
-> [(EquivalenceClass lb,EquivalenceClass lb)]
-- ^ (the @ecpairs@ argument) list of pairs of corresponding
-- equivalence classes of nodes from @gs1@ and @gs2@ that have not
-- been confirmed in 1:1 correspondence with each other. Each
-- pair of equivalence classes contains nodes that must be placed
-- in 1:1 correspondence with each other.
--
-> (Bool,LabelMap lb)
-- ^ the pair @(match, map)@ where @match@ is @True@ if the supplied
-- sets of arcs can be matched, in which case @map@ is a
-- corresponding map from labels to equivalence class identifiers.
-- When @match@ is @False@, @map@ is the most detailed equivalence
-- class map obtained before a mismatch was detected or a guess
-- was required -- this is intended to help identify where the
-- graph mismatch may be.
graphMatch1 guessed matchable gs1 gs2 lmap ecpairs =
let
(secs,mecs) = partition uniqueEc ecpairs
uniqueEc ( (_,[_]) , (_,[_]) ) = True
uniqueEc ( _ , _ ) = False
doMatch ( (_,[l1]) , (_,[l2]) ) = labelMatch matchable lmap l1 l2
doMatch x = error $ "doMatch failue: " ++ show x -- keep -Wall happy
ecEqSize ( (_,ls1) , (_,ls2) ) = length ls1 == length ls2
eSize ( (_,ls1) , _ ) = length ls1
ecCompareSize = comparing eSize
(lmap',mecs',newEc,matchEc) = reclassify gs1 gs2 lmap mecs
match2 = graphMatch2 matchable gs1 gs2 lmap $ sortBy ecCompareSize mecs
in
-- trace ("graphMatch1\nsingle ECs:\n"++show secs++
-- "\nmultiple ECs:\n"++show mecs++
-- "\n\n") $
-- if mismatch in singleton equivalence classes, fail
if not $ all doMatch secs then (False,lmap)
else
-- if no multi-member equivalence classes,
-- check and return label map supplied
-- trace ("graphMatch1\ngraphMapEq: "++show (graphMapEq lmap gs1 gs2)) $
if null mecs then (graphMapEq lmap gs1 gs2,lmap)
else
-- if size mismatch in equivalence classes, fail
-- trace ("graphMatch1\nall ecEqSize mecs: "++show (all ecEqSize mecs)) $
-- invoke reclassification, and deal with result
if not (all ecEqSize mecs) || not matchEc
then (False, lmap)
else if newEc
then graphMatch1 guessed matchable gs1 gs2 lmap' mecs'
-- if guess does not result in a match, return supplied label map
else if fst match2 then match2 else (False, lmap)
{-
if not $ all ecEqSize mecs then (False,lmap)
else
if not matchEc then (False,lmap)
else
if newEc then graphMatch1 guessed matchable gs1 gs2 lmap' mecs'
else
if fst match2 then match2 else (False,lmap)
-}
-- | Auxiliary graph matching function
--
-- This function is called when deterministic decomposition of node
-- mapping equivalence classes has run its course.
--
-- It picks a pair of equivalence classes in ecpairs, and arbitrarily matches
-- pairs of nodes in those equivalence classes, recursively calling the
-- graph matching function until a suitable node mapping is discovered
-- (success), or until all such pairs have been tried (failure).
--
-- This function represents a point to which arbitrary choices are backtracked.
-- The list comprehension 'glp' represents the alternative choices at the
-- point of backtracking
--
-- The selected pair of nodes are placed in a new equivalence class based on their
-- original equivalence class value, but with a new NodeVal generation number.
graphMatch2 :: (Label lb) => (lb -> lb -> Bool)
-> ArcSet lb
-> ArcSet lb
-> LabelMap lb
-> [(EquivalenceClass lb,EquivalenceClass lb)]
-> (Bool,LabelMap lb)
graphMatch2 _ _ _ _ [] = error "graphMatch2 sent an empty list" -- To keep -Wall happy
graphMatch2 matchable gs1 gs2 lmap ((ec1@(ev1,ls1),ec2@(ev2,ls2)):ecpairs) =
let
v1 = snd ev1
-- Return any equivalence-mapping obtained by matching a pair
-- of labels in the supplied list, or Nothing.
try [] = (False,lmap)
try ((l1,l2):lps) = if isEquiv try1 l1 l2 then try1 else try lps
where
try1 = graphMatch1 True matchable gs1 gs2 lmap' ecpairs'
lmap' = newLabelMap lmap [(l1,v1),(l2,v1)]
ecpairs' = ((ev',[l1]),(ev',[l2])):ec':ecpairs
ev' = mapLabelIndex lmap' l1
ec' = (ecRemoveLabel ec1 l1, ecRemoveLabel ec2 l2)
-- [[[TODO: replace this: if isJust try ?]]]
isEquiv (False,_) _ _ = False
isEquiv (True,lm) x1 x2 =
mapLabelIndex m1 x1 == mapLabelIndex m2 x2
where
m1 = remapLabels gs1 lm [x1]
m2 = remapLabels gs2 lm [x2]
-- glp is a list of label-pair candidates for matching,
-- selected from the first label-equivalence class.
-- NOTE: final test is call of external matchable function
glp = [ (l1,l2) | l1 <- ls1 , l2 <- ls2 , matchable l1 l2 ]
in
assert (ev1 == ev2) -- "GraphMatch2: Equivalence class value mismatch" $
$ try glp
-- this was in Swish.Utils.MiscHelpers along with a simple hash-based function
-- based on Sedgewick, Algorithms in C, p233. As we have now moved to using
-- Data.Hashable it is not clear whether this is still necessary or sensible.
--
hashModulus :: Int
hashModulus = 16000001
-- | Returns a string representation of a LabelMap value
--
showLabelMap :: (Label lb) => LabelMap lb -> String
showLabelMap (LabelMap gn lmap) =
"LabelMap gen=" ++ Prelude.show gn ++ ", map=" ++
foldl' (++) "" (map (("\n " ++) . Prelude.show) es)
where
es = M.toList lmap
-- | Map a label to its corresponding label index value in the
-- supplied LabelMap.
--
mapLabelIndex :: (Label lb) => LabelMap lb -> lb -> LabelIndex
mapLabelIndex (LabelMap _ lxms) lb = M.findWithDefault nullLabelVal lb lxms
-- | Confirm that a given pair of labels are matchable, and are
-- mapped to the same value by the supplied label map
--
labelMatch :: (Label lb)
=> (lb -> lb -> Bool) -> LabelMap lb -> lb -> lb -> Bool
labelMatch matchable lmap l1 l2 =
matchable l1 l2 && (mapLabelIndex lmap l1 == mapLabelIndex lmap l2)
-- | Replace selected values in a label map with new values from the supplied
-- list of labels and new label index values. The generation number is
-- supplied from the current label map. The generation number in the
-- resulting label map is incremented.
--
newLabelMap :: (Label lb) => LabelMap lb -> [(lb, Word32)] -> LabelMap lb
newLabelMap lmap [] = newGenerationMap lmap
newLabelMap lmap (lv:lvs) = setLabelHash (newLabelMap lmap lvs) lv
-- | Replace a label and its associated value in a label map
-- with a new value using the supplied hash value and the current
-- `LabelMap` generation number. If the key is not found, then no change
-- is made to the label map.
setLabelHash :: (Label lb)
=> LabelMap lb -> (lb, Word32) -> LabelMap lb
setLabelHash (LabelMap g lmap) (lb,lh) =
LabelMap g $ M.insert lb (g,lh) lmap
-- | Increment the generation of the label map.
--
-- Returns a new label map identical to the supplied value
-- but with an incremented generation number.
--
newGenerationMap :: (Label lb) => LabelMap lb -> LabelMap lb
newGenerationMap (LabelMap g lvs) = LabelMap (g + 1) lvs
-- | Scan label list, assigning initial label map values,
-- adding new values to the label map supplied.
--
-- Label map values are assigned on the basis of the
-- label alone, without regard for it's connectivity in
-- the graph. (cf. `reclassify`).
--
-- All variable node labels are assigned the same initial
-- value, as they may be matched with each other.
--
assignLabelMap :: (Label lb) => S.Set lb -> LabelMap lb -> LabelMap lb
assignLabelMap ns lmap = S.foldl' (flip assignLabelMap1) lmap ns
assignLabelMap1 :: (Label lb) => lb -> LabelMap lb -> LabelMap lb
assignLabelMap1 lab (LabelMap g lvs) =
LabelMap g $ M.insertWith (const id) lab (g, initVal lab) lvs
-- Calculate initial value for a node
initVal :: (Label lb) => lb -> Word32
initVal = fromIntegral . hashVal 0
hashVal :: (Label lb) => Word32 -> lb -> Int
hashVal seed lab =
if labelIsVar lab then 23 `hashWithSalt` seed else labelHash (fromIntegral seed) lab
-- | Return the equivalence classes of the supplied nodes
-- using the label map.
equivalenceClasses ::
(Label lb)
=> LabelMap lb -- ^ label map
-> S.Set lb -- ^ nodes to be reclassified
-> [EquivalenceClass lb]
equivalenceClasses lmap ls =
pairGroup $ S.toList $ S.map labelPair ls
where
labelPair l = (mapLabelIndex lmap l,l)
-- | Reclassify labels
--
-- Examines the supplied label equivalence classes (based on the supplied
-- label map), and evaluates new equivalence subclasses based on node
-- values and adjacency (for variable nodes) and rehashing
-- (for non-variable nodes).
--
-- Note, assumes that all all equivalence classes supplied are
-- non-singletons; i.e. contain more than one label.
--
reclassify ::
(Label lb)
=> ArcSet lb
-- ^ (the @gs1@ argument) the first of two sets of arcs to perform a
-- basis for reclassifying the labels in the first equivalence
-- class in each pair of @ecpairs@.
-> ArcSet lb
-- ^ (the @gs2@ argument) the second of two sets of arcs to perform a
-- basis for reclassifying the labels in the second equivalence
-- class in each pair of the @ecpairs@ argument
-> LabelMap lb
-- ^ the label map used for classification of the labels in
-- the supplied equivalence classes
-> [(EquivalenceClass lb,EquivalenceClass lb)]
-- ^ (the @ecpairs@ argument) a list of pairs of corresponding equivalence classes of
-- nodes from @gs1@ and @gs2@ that have not been confirmed
-- in 1:1 correspondence with each other.
-> (LabelMap lb,[(EquivalenceClass lb,EquivalenceClass lb)],Bool,Bool)
-- ^ The output tuple consists of:
--
-- 1) a revised label map reflecting the reclassification
--
-- 2) a new list of equivalence class pairs based on the
-- new node map
--
-- 3) if the reclassification partitions any of the
-- supplied equivalence classes then `True`, else `False`
--
-- 4) if reclassification results in each equivalence class
-- being split same-sized equivalence classes in the two graphs,
-- then `True`, otherwise `False`.
reclassify gs1 gs2 lmap@(LabelMap _ lm) ecpairs =
assert (gen1 == gen2) -- "Label map generation mismatch"
(LabelMap gen1 lm',ecpairs',newPart,matchPart)
where
LabelMap gen1 lm1 =
remapLabels gs1 lmap $ foldl1 (++) $ map (ecLabels . fst) ecpairs
LabelMap gen2 lm2 =
remapLabels gs2 lmap $ foldl1 (++) $ map (ecLabels . snd) ecpairs
lm' = classifyCombine lm $ M.union lm1 lm2
tmap f (a,b) = (f a, f b)
-- ecGroups :: [([EquivalenceClass lb],[EquivalenceClass lb])]
ecGroups = map (tmap remapEc) ecpairs
ecpairs' = concatMap (uncurry zip) ecGroups
newPart = any pairG1 lenGroups
matchPart = all pairEq lenGroups
lenGroups = map (tmap length) ecGroups
pairEq = uncurry (==)
pairG1 (p1,p2) = p1 > 1 || p2 > 1
remapEc = pairGroup . map (newIndex lm') . pairUngroup
newIndex x (_,lab) = (M.findWithDefault nullLabelVal lab x,lab)
-- Replace the values in lm1 with those from lm2, but do not copy over new
-- keys from lm2
classifyCombine :: (Ord a) => M.Map a b -> M.Map a b -> M.Map a b
classifyCombine = M.mergeWithKey (\_ _ v -> Just v) id (const M.empty)
-- | Calculate a new index value for a supplied set of labels based on the
-- supplied label map and adjacency calculations in the supplied graph
--
remapLabels ::
(Label lb)
=> ArcSet lb -- ^ arcs used for adjacency calculations when remapping
-> LabelMap lb -- ^ the current label index values
-> [lb] -- ^ the graph labels for which new mappings are to be created
-> LabelMap lb
-- ^ the updated label map containing recalculated label index values
-- for the given graph labels. The label map generation number is
-- incremented by 1.
remapLabels gs lmap@(LabelMap gen _) ls =
LabelMap gen' $ M.fromList newEntries
where
gen' = gen + 1
newEntries = [ (l, (gen', fromIntegral (newIndex l))) | l <- ls ]
-- TODO: should review this given the changes to the hash code
-- since it was re-written
newIndex l
| labelIsVar l = mapAdjacent l -- adjacency classifies variable labels
| otherwise = hashVal gen l -- otherwise rehash (to disentangle collisions) TODO: BRANCH IS UNTESTED
-- mapAdjacent used to use `rem` hashModulus
mapAdjacent l = hashModulus `hashWithSalt` sum (sigsOver l)
gls = S.toList gs
sigsOver l = select (hasLabel l) gls (arcSignatures lmap gls)
-- |Select is like filter, except that it tests one list to select
-- elements from a second list.
select :: ( a -> Bool ) -> [a] -> [b] -> [b]
select _ [] [] = []
select f (e1:l1) (e2:l2)
| f e1 = e2 : select f l1 l2
| otherwise = select f l1 l2
select _ _ _ = error "select supplied with different length lists"
-- | Return the set of distinct labels used in the graph.
graphLabels :: (Label lb) => ArcSet lb -> S.Set lb
graphLabels = getComponents arcLabels
-- TODO: worry about overflow?
-- TODO: should probably return a Set of (Int, Arc lb) or something,
-- as may be useful for the calling code
-- | Calculate a signature value for each arc that can be used in
-- constructing an adjacency based value for a node. The adjacancy
-- value for a label is obtained by summing the signatures of all
-- statements containing that label.
--
arcSignatures ::
(Label lb)
=> LabelMap lb -- ^ the current label index values
-> [Arc lb] -- ^ calculate signatures for these arcs
-> [Int] -- ^ the signatures of the arcs
arcSignatures lmap =
map (sigCalc . arcToTriple)
where
sigCalc (s,p,o) =
hashModulus `hashWithSalt`
( labelVal2 s +
labelVal2 p * 3 +
labelVal2 o * 5 )
labelVal = mapLabelIndex lmap
labelVal2 = uncurry (*) . labelVal
-- | Return a new graph that is supplied graph with every node/arc
-- mapped to a new value according to the supplied function.
--
-- Used for testing for graph equivalence under a supplied
-- label mapping; e.g.
--
-- > if ( graphMap nodeMap gs1 ) == ( graphMap nodeMap gs2 ) then (same)
--
graphMap ::
(Label lb)
=> LabelMap lb
-> ArcSet lb
-> ArcSet LabelIndex
graphMap = S.map . fmap . mapLabelIndex
-- | Compare a pair of graphs for equivalence under a given mapping
-- function.
--
-- This is used to perform the ultimate test that two graphs are
-- indeed equivalent: guesswork in `graphMatch2` means that it is
-- occasionally possible to construct a node mapping that generates
-- the required singleton equivalence classes, but does not fully
-- reflect the topology of the graphs.
graphMapEq ::
(Label lb)
=> LabelMap lb
-> ArcSet lb
-> ArcSet lb
-> Bool
graphMapEq lmap = (==) `on` graphMap lmap
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012, 2016, 2018, 2020 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/GraphMem.hs 0000644 0000000 0000000 00000013215 13543702315 015202 0 ustar 00 0000000 0000000 {-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
--------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : GraphMem
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012, 2014, 2016, 2018 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : CPP, FlexibleInstances, MultiParamTypeClasses
--
-- This module defines a simple memory-based graph instance.
--
--------------------------------------------------------------------------------
------------------------------------------------------------
-- Simple labelled directed graph value
------------------------------------------------------------
module Swish.GraphMem
( GraphMem(..)
, LabelMem(..)
, setArcs, getArcs, addGraphs, delete, extract, labels
, labelIsVar, labelHash
-- For debug/test:
, matchGraphMem
) where
import qualified Data.Set as S
import Swish.GraphClass
import Swish.GraphMatch
import Data.Hashable (Hashable(..))
#if (!defined(__GLASGOW_HASKELL__)) || (__GLASGOW_HASKELL__ < 710)
import Data.Monoid (Monoid(..))
#endif
import Data.Ord (comparing)
#if !(MIN_VERSION_base(4, 11, 0))
import Data.Semigroup
#endif
-- | Simple memory-based graph type.
data GraphMem lb = GraphMem { arcs :: ArcSet lb }
instance LDGraph GraphMem lb where
emptyGraph = GraphMem S.empty
getArcs = arcs
setArcs g as = g { arcs=as }
instance (Label lb) => Eq (GraphMem lb) where
(==) = graphEq
instance (Label lb) => Ord (GraphMem lb) where
compare = comparing getArcs
instance (Label lb) => Show (GraphMem lb) where
show = graphShow
instance (Label lb) => Semigroup (GraphMem lb) where
(<>) = addGraphs
instance (Label lb) => Monoid (GraphMem lb) where
mempty = emptyGraph
#if !(MIN_VERSION_base(4, 11, 0))
mappend = (<>)
#endif
graphShow :: (Label lb) => GraphMem lb -> String
graphShow g = "Graph:" ++ S.foldr ((++) . ("\n " ++) . show) "" (arcs g)
-- | Return Boolean graph equality
graphEq :: (Label lb) => GraphMem lb -> GraphMem lb -> Bool
graphEq g1 g2 = fst ( matchGraphMem g1 g2 )
-- | GraphMem matching function accepting GraphMem value and returning
-- node map if successful
--
matchGraphMem ::
(Label lb)
=> GraphMem lb
-> GraphMem lb
-> (Bool,LabelMap (ScopedLabel lb))
-- ^ if the first element is @True@ then the second value is a label
-- map that maps each label to an equivalence-class identifier,
-- otherwise `emptyMap`.
--
matchGraphMem g1 g2 =
let
gs1 = arcs g1
gs2 = arcs g2
matchable l1 l2
| labelIsVar l1 && labelIsVar l2 = True
| labelIsVar l1 || labelIsVar l2 = False
| otherwise = l1 == l2
in
graphMatch matchable gs1 gs2
{-
-- | Return bijection between two graphs, or empty list
graphBiject :: (Label lb) => GraphMem lb -> GraphMem lb -> [(lb,lb)]
graphBiject g1 g2 = if null lmap then [] else zip (sortedls g1) (sortedls g2)
where
lmap = graphMatch g1 g2
sortedls g = map snd $
(sortBy indexComp) $
equivalenceClasses (graphLabels $ arcs g) lmap
classComp ec1 ec2 = indexComp (classIndexVal ec1) (classIndexVal ec2)
indexComp (g1,v1) (g2,v2)
| g1 == g2 = compare v1 v2
| otherwise = compare g1 g2
-}
-- | Minimal graph label value - for testing
data LabelMem
= LF String
| LV String
instance Hashable LabelMem where
hashWithSalt salt (LF l) = salt `hashWithSalt` (1::Int) `hashWithSalt` l
hashWithSalt salt (LV l) = salt `hashWithSalt` (2::Int) `hashWithSalt` l
#if !MIN_VERSION_hashable(1,2,0)
hash (LF l) = 1 `hashWithSalt` l
hash (LV l) = 2 `hashWithSalt` l
#endif
instance Label LabelMem where
labelIsVar (LV _) = True
labelIsVar _ = False
getLocal (LV loc) = loc
getLocal lab = error "getLocal of non-variable label: " ++ show lab
makeLabel = LV
labelHash = hashWithSalt
instance Eq LabelMem where
(LF l1) == (LF l2) = l1 == l2
(LV l1) == (LV l2) = l1 == l2
_ == _ = False
instance Ord LabelMem where
(LF l1) `compare` (LF l2) = l1 `compare` l2
(LV l1) `compare` (LV l2) = l1 `compare` l2
(LF _) `compare` _ = LT
_ `compare` (LF _) = GT
instance Show LabelMem where
show (LF l1) = '!' : l1
show (LV l2) = '?' : l2
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/GraphPartition.hs 0000644 0000000 0000000 00000052254 14220136201 016427 0 ustar 00 0000000 0000000 {-# LANGUAGE CPP #-}
#if (__GLASGOW_HASKELL__ >= 802)
{-# LANGUAGE DerivingStrategies #-}
#endif
--------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : GraphPartition
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011, 2012, 2022 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : CPP, DerivingStrategies
--
-- This module contains functions for partitioning a graph into subgraphs
-- that rooted from different subject nodes.
--
--------------------------------------------------------------------------------
module Swish.GraphPartition
( PartitionedGraph(..), getArcs, getPartitions
, GraphPartition(..), node, toArcs
, partitionGraph, comparePartitions
, partitionShowP
)
where
import Swish.GraphClass (Label(..), Arc(..))
import Control.Monad.State (MonadState(..), State)
import Control.Monad.State (evalState)
import Data.List (foldl', partition)
import Data.List.NonEmpty (NonEmpty(..), (<|))
import Data.Maybe (mapMaybe)
import qualified Data.List.NonEmpty as NE
------------------------------------------------------------
-- Data type for a partitioned graph
------------------------------------------------------------
-- |Representation of a graph as a collection of (possibly nested)
-- partitions. Each node in the graph appears at least once as the
-- root value of a 'GraphPartition' value:
--
-- * Nodes that are the subject of at least one statement appear as
-- the first value of exactly one 'PartSub' constructor, and may
-- also appear in any number of 'PartObj' constructors.
--
-- * Nodes appearing only as objects of statements appear only in
-- 'PartObj' constructors.
data PartitionedGraph lb = PartitionedGraph [GraphPartition lb]
deriving
#if (__GLASGOW_HASKELL__ >= 802)
stock
#endif
(Eq, Show)
-- | Returns all the arcs in the partitioned graph.
getArcs :: PartitionedGraph lb -> [Arc lb]
getArcs (PartitionedGraph ps) = concatMap toArcs ps
-- | Returns a list of partitions.
getPartitions :: PartitionedGraph lb -> [GraphPartition lb]
getPartitions (PartitionedGraph ps) = ps
-- Note: do not use the LabelledPartition local type here since we do
-- not want it to appear in the documentation.
-- | Represent a partition of a graph by a node and (optional) contents.
data GraphPartition lb
= PartObj lb
| PartSub lb (NonEmpty (lb,GraphPartition lb))
-- | Returns the node for the partition.
node :: GraphPartition lb -> lb
node (PartObj ob) = ob
node (PartSub sb _) = sb
-- | Creates a list of arcs from the partition. The empty
-- list is returned for `PartObj`.
toArcs :: GraphPartition lb -> [Arc lb]
toArcs (PartObj _) = []
toArcs (PartSub sb prs) = concatMap toArcs1 $ NE.toList prs
where
toArcs1 (pr,ob) = Arc sb pr (node ob) : toArcs ob
-- | Equality is based on total structural equivalence
-- rather than graph equality.
instance (Label lb) => Eq (GraphPartition lb) where
(PartObj o1) == (PartObj o2) = o1 == o2
(PartSub s1 p1) == (PartSub s2 p2) = s1 == s2 && p1 == p2
_ == _ = False
-- Chose ordering to be "more information" first/smaller (arbitrary choice).
instance (Label lb) => Ord (GraphPartition lb) where
(PartSub s1 p1) `compare` (PartSub s2 p2) = (s1,p1) `compare` (s2,p2)
(PartObj o1) `compare` (PartObj o2) = o1 `compare` o2
(PartSub _ _) `compare` _ = LT
_ `compare` (PartSub _ _) = GT
instance (Label lb) => Show (GraphPartition lb) where
show = partitionShow
-- can we just say
-- partitionShow = partitionShowP ""
-- ?
partitionShow :: (Label lb) => GraphPartition lb -> String
partitionShow (PartObj ob) = show ob
partitionShow (PartSub sb (pr :| prs)) =
"(" ++ show sb ++ " " ++ showpr pr ++ concatMap ((" ; " ++).showpr) prs ++ ")"
where
showpr (a,b) = show a ++ " " ++ show b
-- only used in Swish.Commands
-- | Convert a partition into a string with a leading separator string.
partitionShowP ::
(Label lb) =>
String
-> GraphPartition lb
-> String
partitionShowP _ (PartObj ob) = show ob
partitionShowP pref (PartSub sb (pr :| prs)) =
pref ++ "(" ++ show sb ++ " " ++ showpr pr ++ concatMap (((pref ++ " ; ") ++ ).showpr) prs ++ ")"
where
showpr (a,b) = show a ++ " " ++ partitionShowP (pref ++ " ") b
------------------------------------------------------------
-- Creating partitioned graphs
------------------------------------------------------------
--
-- |Turning a partitioned graph into a flat graph is easy.
-- The interesting challenge is to turn a flat graph into a
-- partitioned graph that is more useful for certain purposes.
-- Currently, I'm interested in:
--
-- (1) isolating differences between graphs
--
-- (2) pretty-printing graphs
--
-- For (1), the goal is to separate subgraphs that are known
-- to be equivalent from subgraphs that are known to be different,
-- such that:
--
-- * different sub-graphs are minimized,
--
-- * different
-- sub-graphs are placed into 1:1 correspondence (possibly with null
-- subgraphs), and
--
-- * only deterministic matching decisions are made.
--
-- For (2), the goal is to decide when a subgraph is to be treated
-- as nested in another partition, or treated as a new top-level partition.
-- If a subgraph is referenced by exactly one graph partition, it should
-- be nested in that partition, otherwise it should be a new top-level
-- partition.
--
-- Strategy. Examining just subject and object nodes:
--
-- * all non-blank subject nodes are the root of a top-level partition
--
-- * blank subject nodes that are not the object of exactly one statement
-- are the root of a top-level partition.
--
-- * blank nodes referenced as the object of exactly 1 statement
-- of an existing partition are the root of a sub-partition of the
-- refering partition.
--
-- * what remain are circular chains of blank nodes not referenced
-- elsewhere: for each such chain, pick a root node arbitrarily.
--
partitionGraph :: (Label lb) => [Arc lb] -> PartitionedGraph lb
partitionGraph [] = PartitionedGraph []
partitionGraph arcs =
makePartitions fixs topv1 intv1
where
(fixs,vars) = partition isNonVar $ collect arcSubj arcs
vars1 = collectMore arcObj arcs vars
(intv,topv) = partition objOnce vars1
intv1 = map stripObj intv
topv1 = map stripObj topv
isNonVar = not . labelIsVar . fst
objOnce = isSingle . snd . snd
isSingle [_] = True
isSingle _ = False
stripObj (k,(s,_)) = (k,s)
-- Local state type for partitioning function
type LabelledArcs lb = (lb, NonEmpty (Arc lb))
type LabelledPartition lb = (lb, GraphPartition lb)
type MakePartitionState lb = ([LabelledArcs lb], [LabelledArcs lb], [LabelledArcs lb])
type PState lb = State (MakePartitionState lb)
makePartitions ::
(Eq lb) =>
[LabelledArcs lb]
-> [LabelledArcs lb]
-> [LabelledArcs lb]
-> PartitionedGraph lb
makePartitions fixs topv intv =
PartitionedGraph $ evalState (makePartitions1 []) (fixs,topv,intv)
-- Use a state monad to keep track of arcs that have been incorporated into
-- the resulting list of graph partitions. The collections of arcs used to
-- generate the list of partitions are supplied as the initial state of the
-- monad (see call of evalState above).
--
makePartitions1 ::
(Eq lb) =>
[LabelledArcs lb]
-> PState lb [GraphPartition lb]
makePartitions1 [] = do
s <- pickNextSubject
if null s then return [] else makePartitions1 s
makePartitions1 (sub:subs) = do
ph <- makePartitions2 sub
pt <- makePartitions1 subs
return $ ph ++ pt
makePartitions2 ::
(Eq lb) =>
LabelledArcs lb
-> PState lb [GraphPartition lb]
makePartitions2 subs = do
(part,moresubs) <- makeStatements subs
moreparts <- if null moresubs
then return []
else makePartitions1 moresubs
return $ part:moreparts
makeStatements ::
(Eq lb) =>
LabelledArcs lb
-> PState lb (GraphPartition lb, [LabelledArcs lb])
makeStatements (sub,stmts) = do
propmore <- mapM makeStatement (NE.toList stmts)
let (props,moresubs) = unzip propmore
return (PartSub sub (NE.fromList props), concat moresubs)
-- return (PartSub sub props, concat moresubs)
makeStatement ::
(Eq lb) =>
Arc lb
-> PState lb (LabelledPartition lb, [LabelledArcs lb])
makeStatement (Arc _ prop obj) = do
intobj <- pickIntSubject obj
(gpobj, moresubs) <- if null intobj
then do
ms <- pickVarSubject obj
return (PartObj obj,ms)
else makeStatements (head intobj)
return ((prop,gpobj), moresubs)
pickNextSubject :: PState lb [LabelledArcs lb]
pickNextSubject = do
(a1,a2,a3) <- get
let (s,st) = case (a1,a2,a3) of
(s1h:s1t,s2,s3) -> ([s1h],(s1t,s2,s3))
([],s2h:s2t,s3) -> ([s2h],([],s2t,s3))
([],[],s3h:s3t) -> ([s3h],([],[],s3t))
([],[],[]) -> ([] ,([],[],[] ))
put st
return s
pickIntSubject :: (Eq lb) =>
lb
-> PState lb [LabelledArcs lb]
pickIntSubject sub = do
(s1,s2,s3) <- get
let varsub = removeBy (\x -> (x ==).fst) sub s3
case varsub of
Just (vs, s3new) -> put (s1,s2,s3new) >> return [vs]
Nothing -> return []
pickVarSubject ::
(Eq lb) =>
lb ->
PState lb [LabelledArcs lb]
pickVarSubject sub = do
(s1,s2,s3) <- get
let varsub = removeBy (\x -> (x ==).fst) sub s2
case varsub of
Just (vs, s2new) -> put (s1,s2new,s3) >> return [vs]
_ -> return []
------------------------------------------------------------
-- Other useful functions
------------------------------------------------------------
-- | Create a list of pairs of corresponding Partitions that
-- are unequal.
comparePartitions :: (Label lb) =>
PartitionedGraph lb
-> PartitionedGraph lb
-> [(Maybe (GraphPartition lb), Maybe (GraphPartition lb))]
comparePartitions (PartitionedGraph gp1) (PartitionedGraph gp2) =
comparePartitions1 (reverse gp1) (reverse gp2)
comparePartitions1 :: (Label lb) =>
[GraphPartition lb]
-> [GraphPartition lb]
-> [(Maybe (GraphPartition lb),Maybe (GraphPartition lb))]
comparePartitions1 pg1 pg2 =
ds ++ [ (Just r1p,Nothing) | r1p<-r1 ]
++ [ (Nothing,Just r2p) | r2p<-r2 ]
where
(ds,r1,r2) = listDifferences comparePartitions2 pg1 pg2
-- Compare two graph partitions, with three possible outcomes:
-- Nothing -> no match
-- Just [] -> total match
-- Just [...] -> partial match, with mismatched sub-partitions listed.
--
-- A partial match occurs when the leading nodes are non-variable and
-- equal, but something else in the partition does not match.
--
-- A complete match can be achieved with variable nodes that have
-- different labels
--
comparePartitions2 :: (Label lb) =>
GraphPartition lb
-> GraphPartition lb
-> Maybe [(Maybe (GraphPartition lb), Maybe (GraphPartition lb))]
comparePartitions2 (PartObj l1) (PartObj l2) =
if matchNodes l1 l2 then Just [] else Nothing
comparePartitions2 pg1@(PartSub l1 p1s) pg2@(PartSub l2 p2s) =
if match then comp1 else Nothing
where
comp1 = case comparePartitions3 l1 l2 p1s p2s of
Nothing -> if matchVar then Nothing
else Just [(Just pg1,Just pg2)]
Just [] -> Just []
Just ps -> {- if matchVar then Nothing else -} Just ps
matchVar = labelIsVar l1 && labelIsVar l2
match = matchVar || l1 == l2
comparePartitions2 pg1 pg2 =
if not (labelIsVar l1) && l1 == l2
then Just [(Just pg1,Just pg2)]
else Nothing
where
l1 = node pg1
l2 = node pg2
comparePartitions3 :: (Label lb) =>
lb
-> lb
-> NonEmpty (LabelledPartition lb)
-> NonEmpty (LabelledPartition lb)
-> Maybe [(Maybe (GraphPartition lb),Maybe (GraphPartition lb))]
comparePartitions3 l1 l2 s1s s2s = Just $
ds ++ [ (Just (PartSub l1 (r1p :| [])),Nothing) | r1p<-r1 ]
++ [ (Nothing,Just (PartSub l2 (r2p :| []))) | r2p<-r2 ]
where
(ds,r1,r2) = listDifferences
(comparePartitions4 l1 l2)
(NE.toList s1s)
(NE.toList s2s)
comparePartitions4 :: (Label lb) =>
lb
-> lb
-> LabelledPartition lb
-> LabelledPartition lb
-> Maybe [(Maybe (GraphPartition lb),Maybe (GraphPartition lb))]
comparePartitions4 _ _ (p1,o1) (p2,o2) =
if matchNodes p1 p2 then comp1 else Nothing
where
comp1 = case comparePartitions2 o1 o2 of
Nothing -> Just [(Just o1,Just o2)]
ds -> ds
matchNodes :: (Label lb) => lb -> lb -> Bool
matchNodes l1 l2
| labelIsVar l1 = labelIsVar l2
| otherwise = l1 == l2
------------------------------------------------------------
-- Helpers
------------------------------------------------------------
-- |Collect a list of items by some comparison of a selected component
-- or other derived value.
--
-- cmp a comparison function that determines if a pair of values
-- should be grouped together
-- sel a function that selects a value from any item
--
-- Example: collect fst [(1,'a'),(2,'b'),(1,'c')] =
-- [(1,[(1,'a'),(1,'c')]),(2,[(2,'b')])]
--
collect :: (Eq b) => (a->b) -> [a] -> [(b, NonEmpty a)]
collect = collectBy (==)
collectBy :: (b->b->Bool) -> (a->b) -> [a] -> [(b, NonEmpty a)]
collectBy cmp sel = map reverseCollection . collectBy1 cmp sel []
collectBy1 :: (b->b->Bool) -> (a->b) -> [(b, NonEmpty a)] -> [a] -> [(b, NonEmpty a)]
collectBy1 _ _ sofar [] = sofar
collectBy1 cmp sel sofar (a:as) =
collectBy1 cmp sel (collectBy2 cmp sel a sofar) as
collectBy2 :: (b->b->Bool) -> (a->b) -> a -> [(b, NonEmpty a)] -> [(b, NonEmpty a)]
collectBy2 _ sel a [] = [(sel a, a :| [])]
collectBy2 cmp sel a (col@(k,as) : cols)
| cmp ka k = (k, a <| as) : cols
| otherwise = col : collectBy2 cmp sel a cols
where
ka = sel a
reverseCollection :: (b, NonEmpty a) -> (b, NonEmpty a)
reverseCollection (k,as) = (k, NE.reverse as)
{-
-- Example/test:
testCollect1 :: [(Int, [(Int, Char)])]
testCollect1 = collect fst [(1,'a'),(2,'b'),(1,'c'),(1,'d'),(2,'d'),(3,'d')]
testCollect2 :: Bool
testCollect2 = testCollect1
== [ (1,[(1,'a'),(1,'c'),(1,'d')])
, (2,[(2,'b'),(2,'d')])
, (3,[(3,'d')])
]
-}
-- |Add new values to an existing list of collections.
-- The list of collections is not extended, but each collection is
-- augmented with a further list of values from the supplied list,
-- each of which are related to the existing collection in some way.
--
-- NOTE: the basic pattern of @collect@ and @collectMore@ is similar,
-- and might be generalized into a common set of core functions.
--
collectMore :: (Eq b) => (a->b) -> [a] -> [(b,c)] -> [(b,(c,[a]))]
collectMore = collectMoreBy (==)
collectMoreBy ::
(b->b->Bool) -> (a->b) -> [a] -> [(b,c)] -> [(b,(c,[a]))]
collectMoreBy cmp sel as cols =
map reverseMoreCollection $
collectMoreBy1 cmp sel as (map (\ (b,cs) -> (b,(cs,[])) ) cols)
collectMoreBy1 ::
(b->b->Bool) -> (a->b) -> [a] -> [(b,(c,[a]))] -> [(b,(c,[a]))]
collectMoreBy1 _ _ [] cols = cols
collectMoreBy1 cmp sel (a:as) cols =
collectMoreBy1 cmp sel as (collectMoreBy2 cmp sel a cols)
collectMoreBy2 ::
(b->b->Bool) -> (a->b) -> a -> [(b,(c,[a]))] -> [(b,(c,[a]))]
collectMoreBy2 _ _ _ [] = []
collectMoreBy2 cmp sel a (col@(k,(b,as)):cols)
| cmp (sel a) k = (k,(b, a:as)):cols
| otherwise = col:collectMoreBy2 cmp sel a cols
reverseMoreCollection :: (b,(c,[a])) -> (b,(c,[a]))
reverseMoreCollection (k,(c,as)) = (k,(c,reverse as))
{-
-- Example/test:
testCollectMore1 =
collectMore snd [(111,1),(112,1),(211,2),(311,3),(411,4)] testCollect1
testCollectMore2 :: Bool
testCollectMore2 = testCollectMore1
== [ (1,([(1,'a'),(1,'c'),(1,'d')],[(111,1),(112,1)]))
, (2,([(2,'b'),(2,'d')],[(211,2)]))
, (3,([(3,'d')],[(311,3)]))
]
-}
-- |Remove supplied element from a list using the supplied test
-- function, and return Just the element removed and the
-- remaining list, or Nothing if no element was matched for removal.
--
{-
remove :: (Eq a) => a -> [a] -> Maybe (a,[a])
remove = removeBy (==)
testRemove1 = remove 3 [1,2,3,4,5]
testRemove2 = testRemove1 == Just (3,[1,2,4,5])
testRemove3 = remove 3 [1,2,4,5]
testRemove4 = testRemove3 == Nothing
testRemove5 = remove 5 [1,2,4,5]
testRemove6 = testRemove5 == Just (5,[1,2,4])
testRemove7 = remove 1 [1,2,4]
testRemove8 = testRemove7 == Just (1,[2,4])
testRemove9 = remove 2 [2]
testRemove10 = testRemove9 == Just (2,[])
-}
removeBy :: (b->a->Bool) -> b -> [a] -> Maybe (a,[a])
removeBy cmp a0 as = removeBy1 cmp a0 as []
removeBy1 :: (b->a->Bool) -> b -> [a] -> [a] -> Maybe (a,[a])
removeBy1 _ _ [] _ = Nothing
removeBy1 cmp a0 (a:as) sofar
| cmp a0 a = Just (a,reverseTo sofar as)
| otherwise = removeBy1 cmp a0 as (a:sofar)
-- |Reverse first argument, prepending the result to the second argument
--
reverseTo :: [a] -> [a] -> [a]
reverseTo front back = foldl' (flip (:)) back front
-- |Remove each element from a list, returning a list of pairs,
-- each of which is the element removed and the list remaining.
--
removeEach :: [a] -> [(a,[a])]
removeEach [] = []
removeEach (a:as) = (a,as):[ (a1,a:a1s) | (a1,a1s) <- removeEach as ]
{-
testRemoveEach1 = removeEach [1,2,3,4,5]
testRemoveEach2 = testRemoveEach1 ==
[ (1,[2,3,4,5])
, (2,[1,3,4,5])
, (3,[1,2,4,5])
, (4,[1,2,3,5])
, (5,[1,2,3,4])
]
-}
-- |List differences between the members of two lists, where corresponding
-- elements may appear at arbitrary locations in the corresponding lists.
--
-- Elements are compared using the function 'cmp', which returns:
-- * Nothing if the elements are completely unrelated
-- * Just [] if the elements are identical
-- * Just ds if the elements are related but not identical, in which case
-- ds is a list of values describing differences between them.
--
-- Returns (ds,u1,u2), where:
-- ds is null if the related elements from each list are identical,
-- otherwise is a list of differences between the related elements.
-- u1 is a list of elements in a1 not related to elements in a2.
-- u2 is a list of elements in a2 not related to elements in a1.
--
listDifferences :: (a->a->Maybe [d]) -> [a] -> [a] -> ([d],[a],[a])
listDifferences _ [] a2s = ([],[],a2s)
listDifferences cmp (a1:a1t) a2s =
case mcomp of
Nothing -> morediffs [] [a1] a1t a2s
Just (ds,a2t) -> morediffs ds [] a1t a2t
where
-- mcomp finds identical match, if there is one, or
-- the first element in a2s related to a1, or Nothing
-- [choose was listToMaybe,
-- but that didn't handle repeated properties well]
mcomp = choose $ mapMaybe maybeResult comps
comps = [ (cmp a1 a2,a2t) | (a2,a2t) <- removeEach a2s ]
maybeResult (Nothing,_) = Nothing
maybeResult (Just ds,a2t) = Just (ds,a2t)
morediffs xds xa1h xa1t xa2t = (xds ++ xds1, xa1h ++ xa1r, xa2r)
where
(xds1,xa1r,xa2r) = listDifferences cmp xa1t xa2t
choose [] = Nothing
choose ds@(d:_) = choose1 d ds
choose1 _ (d@([],_):_) = Just d
choose1 d [] = Just d
choose1 d (_:ds) = choose1 d ds
{-
testcmp (l1,h1) (l2,h2)
| (l1 >= h2) || (l2 >= h1) = Nothing
| (l1 == l2) && (h1 == h2) = Just []
| otherwise = Just [((l1,h1),(l2,h2))]
testdiff1 = listDifferences testcmp
[(12,15),(1,2),(3,4),(5,8),(10,11)]
[(10,11),(0,1),(3,4),(6,9),(13,15)]
testdiff2 = testdiff1 == ([((12,15),(13,15)),((5,8),(6,9))],[(1,2)],[(0,1)])
-}
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012, 2022 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/Monad.hs 0000644 0000000 0000000 00000021333 14220136201 014524 0 ustar 00 0000000 0000000 {-# LANGUAGE CPP #-}
#if (__GLASGOW_HASKELL__ >= 802)
{-# LANGUAGE DerivingStrategies #-}
#endif
--------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : Monad
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012, 2022 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : CPP, DerivingStrategies
--
-- Composed state and IO monad for Swish
--
--------------------------------------------------------------------------------
module Swish.Monad
( SwishStateIO, SwishState(..), SwishStatus(..)
, SwishFormat(..)
, NamedGraphMap
-- * Create and modify the Swish state
, emptyState
, setFormat, setBase, setGraph
, modGraphs, findGraph, findFormula
, modRules, findRule
, modRulesets, findRuleset
, findOpenVarModify, findDatatype
, setInfo, resetInfo, setError, resetError
, setStatus
-- * Error handling
, swishError
, reportLine
)
where
import Swish.Namespace (ScopedName, getScopeNamespace)
import Swish.QName (QName)
import Swish.Ruleset (getMaybeContextAxiom, getMaybeContextRule)
import Swish.Rule(Formula(..))
import Swish.RDF.Datatype (RDFDatatype)
import Swish.RDF.Graph (RDFGraph, emptyRDFGraph)
import Swish.RDF.Ruleset (RDFFormula, RDFRule, RDFRuleMap, RDFRuleset, RDFRulesetMap)
import Swish.RDF.VarBinding (RDFOpenVarBindingModify)
import Swish.RDF.BuiltIn (findRDFOpenVarBindingModifier, findRDFDatatype, rdfRulesetMap)
import Control.Monad.Trans (MonadTrans(..))
import Control.Monad.State (StateT(..), modify)
import Data.List (nub)
import System.IO (hPutStrLn, stderr)
import qualified Data.Map as M
{-|
The supported input and output formats.
-}
data SwishFormat =
Turtle -- ^ Turtle format
| N3 -- ^ N3 format
| NT -- ^ NTriples format
deriving
#if (__GLASGOW_HASKELL__ >= 802)
stock
#endif
Eq
instance Show SwishFormat where
show N3 = "N3"
show NT = "Ntriples"
show Turtle = "Turtle"
-- show RDF = "RDF/XML"
-- | The State for a Swish \"program\".
data SwishState = SwishState
{ format :: SwishFormat -- ^ format to use for I/O
, base :: Maybe QName -- ^ base to use rather than file name
, graph :: RDFGraph -- ^ current graph
, graphs :: NamedGraphMap -- ^ script processor named graphs
, rules :: RDFRuleMap -- ^ script processor named rules
, rulesets :: RDFRulesetMap -- ^ script processor rulesets
, infomsg :: Maybe String -- ^ information message, or Nothing
, errormsg :: Maybe String -- ^ error message, or Nothing
, exitcode :: SwishStatus -- ^ current status
}
-- | Status of the processor
--
data SwishStatus =
SwishSuccess -- ^ successful run
| SwishGraphCompareError -- ^ graphs do not compare
| SwishDataInputError -- ^ input data problem (ie format/syntax)
| SwishDataAccessError -- ^ data access error
| SwishArgumentError -- ^ command-line argument error
| SwishExecutionError -- ^ error executing a Swish script
deriving
#if (__GLASGOW_HASKELL__ >= 802)
stock
#endif
(Eq, Enum)
instance Show SwishStatus where
show SwishSuccess = "Success."
show SwishGraphCompareError = "The graphs do not compare as equal."
show SwishDataInputError = "There was a format or syntax error in the input data."
show SwishDataAccessError = "There was a problem accessing data."
show SwishArgumentError = "Argument error: use -h or -? for help."
show SwishExecutionError = "There was a problem executing a Swish script."
-- | The state monad used in executing Swish programs.
type SwishStateIO a = StateT SwishState IO a
-- | The default state for Swish: no loaded graphs or rules, and format
-- set to 'N3'.
emptyState :: SwishState
emptyState = SwishState
{ format = N3
, base = Nothing
, graph = emptyRDFGraph
, graphs = M.empty
, rules = M.empty
, rulesets = rdfRulesetMap
, infomsg = Nothing
, errormsg = Nothing
, exitcode = SwishSuccess
}
-- | Change the format.
setFormat :: SwishFormat -> SwishState -> SwishState
setFormat fm state = state { format = fm }
-- | Change (or remove) the base URI.
setBase :: Maybe QName -> SwishState -> SwishState
setBase bs state = state { base = bs }
-- | Change the current graph.
setGraph :: RDFGraph -> SwishState -> SwishState
setGraph gr state = state { graph = gr }
-- | Modify the named graphs.
modGraphs ::
( NamedGraphMap -> NamedGraphMap ) -> SwishState -> SwishState
modGraphs grmod state = state { graphs = grmod (graphs state) }
-- | Find a named graph.
findGraph :: ScopedName -> SwishState -> Maybe [RDFGraph]
findGraph nam state = M.lookup nam (graphs state)
-- | Find a formula. The search is first made in the named graphs
-- and then, if not found, the rulesets.
findFormula :: ScopedName -> SwishState -> Maybe RDFFormula
findFormula nam state = case findGraph nam state of
Nothing -> getMaybeContextAxiom nam (nub $ M.elems $ rulesets state)
Just [] -> Just $ Formula nam emptyRDFGraph
Just grs -> Just $ Formula nam (head grs)
-- | Modify the named rules.
modRules ::
( RDFRuleMap -> RDFRuleMap ) -> SwishState -> SwishState
modRules rlmod state = state { rules = rlmod (rules state) }
-- | Find a named rule.
findRule :: ScopedName -> SwishState -> Maybe RDFRule
findRule nam state =
case M.lookup nam (rules state) of
Nothing -> getMaybeContextRule nam $ nub $ M.elems $ rulesets state
justlr -> justlr
-- | Modify the rule sets.
modRulesets ::
( RDFRulesetMap -> RDFRulesetMap ) -> SwishState -> SwishState
modRulesets rsmod state = state { rulesets = rsmod (rulesets state) }
-- | Find a rule set.
findRuleset ::
ScopedName -> SwishState -> Maybe RDFRuleset
findRuleset nam state = M.lookup (getScopeNamespace nam) (rulesets state)
-- | Find a modify rule.
findOpenVarModify :: ScopedName -> SwishState -> Maybe RDFOpenVarBindingModify
findOpenVarModify nam _ = findRDFOpenVarBindingModifier nam
-- | Find a data type declaration.
findDatatype :: ScopedName -> SwishState -> Maybe RDFDatatype
findDatatype nam _ = findRDFDatatype nam
-- | Set the information message.
setInfo :: String -> SwishState -> SwishState
setInfo msg state = state { infomsg = Just msg }
-- | Clear the information message.
resetInfo :: SwishState -> SwishState
resetInfo state = state { infomsg = Nothing }
-- | Set the error message.
setError :: String -> SwishState -> SwishState
setError msg state = state { errormsg = Just msg }
-- | Clear the error message.
resetError :: SwishState -> SwishState
resetError state = state { errormsg = Nothing }
-- | Set the status.
setStatus :: SwishStatus -> SwishState -> SwishState
setStatus ec state = state { exitcode = ec }
{-
setVerbose :: Bool -> SwishState -> SwishState
setVerbose f state = state { banner = f }
-}
{-
-- | The graphs dictionary contains named graphs and/or lists
-- of graphs that are created and used by script statements.
data NamedGraph = NamedGraph
{ ngName :: ScopedName
, ngGraph :: [RDFGraph]
}
-}
-- | A LookupMap for the graphs dictionary.
type NamedGraphMap = M.Map ScopedName [RDFGraph]
-- | Report error and set exit status code
swishError :: String -> SwishStatus -> SwishStateIO ()
swishError msg sts = do
mapM_ reportLine [msg, show sts ++ "\n"]
modify $ setStatus sts
-- | Output the text to the standard error stream (a new line is
-- added to the output).
reportLine :: String -> SwishStateIO ()
reportLine = lift . hPutStrLn stderr
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012, 2022 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/Namespace.hs 0000644 0000000 0000000 00000020521 13543702315 015374 0 ustar 00 0000000 0000000 {-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
--------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : Namespace
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012, 2014 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : CPP, OverloadedStrings
--
-- This module defines algebraic datatypes for namespaces and scoped names.
--
-- For these purposes, a namespace is a prefix and URI used to identify
-- a namespace (cf. XML namespaces), and a scoped name is a name that
-- is scoped by a specified namespace.
--
--------------------------------------------------------------------------------
module Swish.Namespace
( Namespace
, makeNamespace, makeNamespaceQName
, getNamespacePrefix, getNamespaceURI, getNamespaceTuple
-- , nullNamespace
, ScopedName
, getScopeNamespace, getScopeLocal
, getScopePrefix, getScopeURI
, getQName, getScopedNameURI
, matchName
, makeScopedName
, makeQNameScopedName
, makeURIScopedName
, makeNSScopedName
, nullScopedName
, namespaceToBuilder
)
where
import Swish.QName (QName, LName, newQName, getLName, emptyLName, getQNameURI, getNamespace, getLocalName)
import Data.Maybe (fromMaybe)
#if (!defined(__GLASGOW_HASKELL__)) || (__GLASGOW_HASKELL__ < 710)
import Data.Monoid (Monoid(..))
#endif
import Data.Ord (comparing)
import Data.String (IsString(..))
import Network.URI (URI(..), parseURIReference, nullURI)
import qualified Data.Text as T
import qualified Data.Text.Lazy.Builder as B
------------------------------------------------------------
-- Namespace, having a prefix and a URI
------------------------------------------------------------
-- |A NameSpace value consists of an optional prefix and a corresponding URI.
--
data Namespace = Namespace (Maybe T.Text) URI
-- data Namespace = Namespace (Maybe T.Text) !URI
-- TODO: look at interning the URI
-- | Returns the prefix stored in the name space.
getNamespacePrefix :: Namespace -> Maybe T.Text
getNamespacePrefix (Namespace p _) = p
-- | Returns the URI stored in the name space.
getNamespaceURI :: Namespace -> URI
getNamespaceURI (Namespace _ u) = u
-- | Convert the name space to a (prefix, URI) tuple.
getNamespaceTuple :: Namespace -> (Maybe T.Text, URI)
getNamespaceTuple (Namespace p u) = (p, u)
-- | Equality is defined by the URI, not by the prefix
-- (so the same URI with different prefixes will be
-- considered to be equal).
instance Eq Namespace where
(Namespace _ u1) == (Namespace _ u2) = u1 == u2
instance Ord Namespace where
-- using show for the URI is wasteful
(Namespace a1 b1) `compare` (Namespace a2 b2) =
(a1, show b1) `compare` (a2, show b2)
instance Show Namespace where
show (Namespace (Just p) u) = show p ++ ":<" ++ show u ++ ">"
show (Namespace _ u) = "<" ++ show u ++ ">"
-- | Create a name space from a URI and an optional prefix label.
makeNamespace ::
Maybe T.Text -- ^ optional prefix.
-> URI -- ^ URI.
-> Namespace
makeNamespace = Namespace
-- | Create a qualified name by combining the URI from
-- the name space with a local component.
makeNamespaceQName ::
Namespace -- ^ The name space URI is used in the qualified name
-> LName -- ^ local component of the qualified name (can be 'emptyLName')
-> QName
makeNamespaceQName (Namespace _ uri) = newQName uri
{-
nullNamespace :: Namespace
nullNamespace = Namespace Nothing ""
-}
-- | Utility routine to create a \@prefix line (matching N3/Turtle)
-- grammar for this namespace.
--
namespaceToBuilder :: Namespace -> B.Builder
namespaceToBuilder (Namespace pre uri) =
mconcat $ map B.fromText
[ "@prefix ", fromMaybe "" pre, ": <", T.pack (show uri), "> .\n"]
------------------------------------------------------------
-- ScopedName, made from a namespace and a local name
------------------------------------------------------------
-- | A full ScopedName value has a QName prefix, namespace URI
-- and a local part. ScopedName values may omit the prefix
-- (see 'Namespace') or the local part.
--
-- Some applications may handle null namespace URIs as meaning
-- the local part is relative to some base URI.
--
data ScopedName = ScopedName !QName Namespace LName
-- | Returns the local part.
getScopeLocal :: ScopedName -> LName
getScopeLocal (ScopedName _ _ l) = l
-- | Returns the namespace.
getScopeNamespace :: ScopedName -> Namespace
getScopeNamespace (ScopedName _ ns _) = ns
-- | Returns the prefix of the namespace, if set.
getScopePrefix :: ScopedName -> Maybe T.Text
getScopePrefix = getNamespacePrefix . getScopeNamespace
-- | Returns the URI of the namespace.
getScopeURI :: ScopedName -> URI
getScopeURI = getNamespaceURI . getScopeNamespace
-- | This is not total since it will fail if the input string is not a valid 'URI'.
instance IsString ScopedName where
fromString s =
maybe (error ("Unable to convert " ++ s ++ " into a ScopedName"))
makeURIScopedName (parseURIReference s)
-- | Scoped names are equal if their corresponding 'QName' values are equal.
instance Eq ScopedName where
sn1 == sn2 = getQName sn1 == getQName sn2
-- | Scoped names are ordered by their 'QName' components.
instance Ord ScopedName where
compare = comparing getQName
-- | If there is a namespace associated then the Show instance
-- uses @prefix:local@, otherwise @@.
instance Show ScopedName where
show (ScopedName qn n l) = case getNamespacePrefix n of
Just pre -> T.unpack $ mconcat [pre, ":", getLName l]
_ -> show qn -- "<" ++ show (getNamespaceURI n) ++ T.unpack l ++ ">"
-- |Get the QName corresponding to a scoped name.
getQName :: ScopedName -> QName
getQName (ScopedName qn _ _) = qn
-- |Get URI corresponding to a scoped name (using RDF conventions).
getScopedNameURI :: ScopedName -> URI
getScopedNameURI = getQNameURI . getQName
-- |Test if supplied string matches the display form of a
-- scoped name.
matchName :: String -> ScopedName -> Bool
matchName str nam = str == show nam
-- |Construct a ScopedName.
makeScopedName ::
Maybe T.Text -- ^ prefix for the namespace
-> URI -- ^ namespace
-> LName -- ^ local name
-> ScopedName
makeScopedName pre nsuri local =
ScopedName (newQName nsuri local)
(Namespace pre nsuri)
local
-- |Construct a ScopedName from a QName.
makeQNameScopedName ::
Maybe T.Text -- ^ prefix
-> QName
-> ScopedName
makeQNameScopedName pre qn = ScopedName qn (Namespace pre (getNamespace qn)) (getLocalName qn)
-- could use qnameFromURI to find a local name if there is one.
-- | Construct a ScopedName for a bare URI (the label is set to \"\").
makeURIScopedName :: URI -> ScopedName
makeURIScopedName uri = makeScopedName Nothing uri emptyLName
-- | Construct a ScopedName.
makeNSScopedName ::
Namespace -- ^ namespace
-> LName -- ^ local component
-> ScopedName
makeNSScopedName ns local =
ScopedName (newQName (getNamespaceURI ns) local) ns local
-- | This should never appear as a valid name
nullScopedName :: ScopedName
nullScopedName = makeURIScopedName nullURI
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/Proof.hs 0000644 0000000 0000000 00000027160 14220136201 014557 0 ustar 00 0000000 0000000 {-# LANGUAGE CPP #-}
#if (__GLASGOW_HASKELL__ >= 802)
{-# LANGUAGE DerivingStrategies #-}
#endif
--------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : Proof
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2016, 2022 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : CPP, DerivingStrategies
--
-- This module defines a framework for constructing proofs
-- over some expression form. It is intended to be used
-- with RDF graphs, but the structures aim to be quite
-- generic with respect to the expression forms allowed.
--
-- It does not define any proof-finding strategy.
--
--------------------------------------------------------------------------------
module Swish.Proof
( Proof(..), Step(..)
, checkProof, explainProof, checkStep, showProof, showsProof, showsFormula )
where
import Swish.Rule (Expression(..), Formula(..), Rule(..))
import Swish.Rule (showsFormula, showsFormulae)
import Swish.Ruleset (Ruleset(..))
import Data.List (union, intersect, intercalate, foldl')
import Data.Maybe (catMaybes, isNothing)
import Data.String.ShowLines (ShowLines(..))
import qualified Data.Set as S
------------------------------------------------------------
-- Proof framework
------------------------------------------------------------
-- |Step in proof chain
--
-- The display name for a proof step comes from the display name of its
-- consequence formula.
data Step ex = Step
{ stepRule :: Rule ex -- ^ Inference rule used
, stepAnt :: [Formula ex] -- ^ Antecedents of inference rule
, stepCon :: Formula ex -- ^ Named consequence of inference rule
} deriving
#if (__GLASGOW_HASKELL__ >= 802)
stock
#endif
Show
-- |Proof is a structure that presents a chain of rule applications
-- that yield a result expression from a given expression
data Proof ex = Proof
{ proofContext :: [Ruleset ex] -- ^ Proof context: list of rulesets,
-- each of which provides a number of
-- axioms and rules.
, proofInput :: Formula ex -- ^ Given expression
, proofResult :: Formula ex -- ^ Result expression
, proofChain :: [Step ex] -- ^ Chain of inference rule applications
-- progressing from input to result
}
-- |Return a list of axioms from all the rulesets in a proof
proofAxioms :: Proof a -> [Formula a]
proofAxioms = concatMap rsAxioms . proofContext
-- |Return a list of rules from all the rulesets in a proof
proofRules :: Proof a -> [Rule a]
proofRules = concatMap rsRules . proofContext
-- |Return list of axioms actually referenced by a proof
proofAxiomsUsed :: Proof ex -> [Formula ex]
proofAxiomsUsed proof = foldl' union [] $ map stepAxioms (proofChain proof)
where
stepAxioms st = stepAnt st `intersect` proofAxioms proof
-- |Check consistency of given proof.
-- The supplied rules and axioms are assumed to be correct.
checkProof ::
(Expression ex, Ord ex)
=> Proof ex -> Bool
checkProof pr =
checkProof1 (proofRules pr) initExpr (proofChain pr) goalExpr
where
initExpr = formExpr (proofInput pr) : map formExpr (proofAxioms pr)
goalExpr = formExpr $ proofResult pr
checkProof1 ::
(Expression ex, Ord ex)
=> [Rule ex] -> [ex] -> [Step ex] -> ex -> Bool
checkProof1 _ prev [] res = res `elem` prev
checkProof1 rules prev (st:steps) res =
checkStep rules prev st &&
checkProof1 rules (formExpr (stepCon st):prev) steps res
-- | A proof step is valid if rule is in list of rules
-- and the antecedents are sufficient to obtain the conclusion
-- and the antecedents are in the list of formulae already proven.
--
-- Note: this function depends on the ruleName of any rule being
-- unique among all rules. In particular the name of the step rule
-- being in correspondence with the name of one of the indicated
-- valid rules of inference.
checkStep ::
Ord ex
=> [Rule ex] -- ^ rules
-> [ex] -- ^ antecedants
-> Step ex -- ^ the step to validate
-> Bool -- ^ @True@ if the step is valid
checkStep rules prev step = isNothing $ explainStep rules prev step
{-
Is the following an optimisation of the above?
checkStep rules prev step =
-- Rule name is one of supplied rules, and
(ruleName srul `elem` map ruleName rules) &&
-- Antecedent expressions are all previously accepted expressions
(sant `subset` prev) &&
-- Inference rule yields concequence from antecendents
checkInference srul sant scon
where
-- Rule from proof step:
srul = stepRule step
-- Antecedent expressions from proof step:
sant = map formExpr $ stepAnt step
-- Consequentent expression from proof step:
scon = formExpr $ stepCon step
-}
{-
(formExpr (stepCon step) `elem` sfwd)
-- (or $ map (`subset` sant) sbwd)
where
-- Rule from proof step:
srul = stepRule step
-- Antecedent expressions from proof step:
sant = map formExpr $ stepAnt step
-- Forward chaining from antecedents of proof step
scon = map formExpr $ stepCon step
-- Forward chaining from antecedents of proof step
sfwd = fwdApply srul sant
-- Backward chaining from consequent of proof step
-- (Does not work because of introduction of existentials)
sbwd = bwdApply srul (formExpr $ stepCon step)
-}
-- |Check proof. If there is an error then return information
-- about the failing step.
explainProof ::
(Expression ex, Ord ex) => Proof ex -> Maybe String
explainProof pr =
explainProof1 (proofRules pr) initExpr (proofChain pr) goalExpr
where
initExpr = formExpr (proofInput pr) : map formExpr (proofAxioms pr)
goalExpr = formExpr $ proofResult pr
explainProof1 ::
(Expression ex, Ord ex)
=> [Rule ex] -> [ex] -> [Step ex] -> ex -> Maybe String
explainProof1 _ prev [] res =
if res `elem` prev then Nothing else Just "Result not demonstrated"
explainProof1 rules prev (st:steps) res =
case explainStep rules prev st of
Nothing -> explainProof1 rules (formExpr (stepCon st):prev) steps res
Just ex -> Just ("Invalid step: " ++ show (formName $ stepCon st) ++ ": " ++ ex)
-- | A proof step is valid if rule is in list of rules
-- and the antecedents are sufficient to obtain the conclusion
-- and the antecedents are in the list of formulae already proven.
--
-- Note: this function depends on the ruleName of any rule being
-- unique among all rules. In particular the name of the step rule
-- being in correspondence with the name of one of the indicated
-- valid rules of inference.
--
explainStep ::
Ord ex
=> [Rule ex] -- ^ rules
-> [ex] -- ^ previous
-> Step ex -- ^ step
-> Maybe String -- ^ @Nothing@ if step is okay, otherwise a string indicating the error
explainStep rules prev step =
if null errors then Nothing else Just $ intercalate ", " errors
where
-- Rule from proof step:
srul = stepRule step
-- Antecedent expressions from proof step:
sant = map formExpr $ stepAnt step
-- Consequentent expression from proof step:
scon = formExpr $ stepCon step
-- Tests for step to be valid
errors = catMaybes
-- Rule name is one of supplied rules, and
[ require (ruleName srul `elem` map ruleName rules)
("rule " ++ show (ruleName srul) ++ " not present")
-- Antecedent expressions are all previously accepted expressions
, require (S.fromList sant `S.isSubsetOf` S.fromList prev) -- (sant `subset` prev)
"antecedent not axiom or previous result"
-- Inference rule yields consequence from antecedents
, require (checkInference srul sant scon)
"rule does not deduce consequence from antecedents"
]
require b s = if b then Nothing else Just s
-- |Create a displayable form of a proof, returned as a `ShowS` value.
--
-- This function is intended to allow the calling function some control
-- of multiline displays by providing:
--
-- (1) the first line of the proof is not preceded by any text, so
-- it may be appended to some preceding text on the same line,
--
-- (2) the supplied newline string is used to separate lines of the
-- formatted text, and may include any desired indentation, and
--
-- (3) no newline is output following the final line of text.
showsProof ::
(ShowLines ex)
=> String -- ^ newline string
-> Proof ex
-> ShowS
showsProof newline proof =
if null axioms then shProof else shAxioms . shProof
where
axioms = proofAxiomsUsed proof
shAxioms =
showString ("Axioms:" ++ newline) .
showsFormulae newline (proofAxiomsUsed proof) newline
shProof =
showString ("Input:" ++ newline) .
showsFormula newline (proofInput proof) .
showString (newline ++ "Proof:" ++ newline) .
showsSteps newline (proofChain proof)
-- |Returns a simple string representation of a proof.
showProof ::
(ShowLines ex)
=> String -- ^ newline string
-> Proof ex
-> String
showProof newline proof = showsProof newline proof ""
-- |Create a displayable form of a list of labelled proof steps
showsSteps :: (ShowLines ex) => String -> [Step ex] -> ShowS
showsSteps _ [] = id
showsSteps newline [s] = showsStep newline s
showsSteps newline (s:ss) = showsStep newline s .
showString newline .
showsSteps newline ss
-- |Create a displayable form of a labelled proof step.
showsStep :: (ShowLines ex) => String -> Step ex -> ShowS
showsStep newline s = showsFormula newline (stepCon s) .
showString newline .
showString (" (by [" ++ rulename ++ "] from " ++ antnames ++ ")")
where
rulename = show . ruleName $ stepRule s
antnames = showNames $ map (show . formName) (stepAnt s)
-- |Return a string containing a list of names.
showNames :: [String] -> String
showNames [] = ""
showNames [n] = showName n
showNames [n1,n2] = showName n1 ++ " and " ++ showName n2
showNames (n1:ns) = showName n1 ++ ", " ++ showNames ns
-- |Return a string representing a single name.
showName :: String -> String
showName n = "[" ++ n ++ "]"
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011, 2016, 2022 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/QName.hs 0000644 0000000 0000000 00000023611 14220136201 014470 0 ustar 00 0000000 0000000 {-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
#if (__GLASGOW_HASKELL__ >= 802)
{-# LANGUAGE DerivingStrategies #-}
#endif
--------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : QName
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011, 2012, 2013, 2020, 2022 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : CPP, DerivingStrategies, OverloadedStrings
--
-- This module defines an algebraic datatype for qualified names (QNames),
-- which represents a 'URI' as the combination of a namespace 'URI'
-- and a local component ('LName'), which can be empty.
--
-- Although RDF supports using IRIs, the use of 'URI' here precludes this,
-- which means that, for instance, 'LName' only accepts a subset of valid
-- characters. There is currently no attempt to convert from an IRI into a URI.
--
--------------------------------------------------------------------------------
-- At present we support using URI references rather than forcing an absolute
-- URI. This is partly to support the existing tests (too lazy to resolve whether
-- the tests really should be using relative URIs in this case).
module Swish.QName
( QName
, LName
, emptyLName
, newLName
, getLName
, newQName
, qnameFromURI
, getNamespace
, getLocalName
, getQNameURI
, qnameFromFilePath
)
where
import Data.Char (isAscii)
import Data.Maybe (fromMaybe)
import Data.Interned (intern, unintern)
import Data.Interned.URI (InternedURI)
import Data.Ord (comparing)
import Data.String (IsString(..))
import Network.URI (URI(..), URIAuth(..), parseURIReference)
import System.Directory (canonicalizePath)
import System.FilePath (splitFileName)
import qualified Data.Text as T
------------------------------------------------------------
-- Qualified name
------------------------------------------------------------
--
-- These are RDF QNames rather than XML ones (as much as
-- RDF can claim to have them).
--
{-| A local name, which can be empty.
At present, the local name can not contain a space character and can only
contain ascii characters (those that match 'Data.Char.isAscii').
In version @0.9.0.3@ and earlier, the following characters were not
allowed in local names: \'#\', \':\', or \'/\' characters.
This is all rather experimental.
-}
newtype LName = LName T.Text
deriving
#if (__GLASGOW_HASKELL__ >= 802)
stock
#endif
(Eq, Ord)
instance Show LName where
show (LName t) = show t
-- | This is not total since attempting to convert a string
-- containing invalid characters will cause an error.
instance IsString LName where
fromString s =
fromMaybe (error ("Invalid local name: " ++ s)) $
newLName (T.pack s)
-- | The empty local name.
emptyLName :: LName
emptyLName = LName ""
-- | Create a local name.
newLName :: T.Text -> Maybe LName
-- newLName l = if T.any (`elem` " #:/") l then Nothing else Just (LName l) -- 0.7.0.1 and earlier
-- newLName l = if T.any (\c -> c `elem` " #:/" || not (isAscii c)) l then Nothing else Just (LName l) -- 0.9.0.3 and earlier
newLName l = if T.any (\c -> c == ' ' || not (isAscii c)) l then Nothing else Just (LName l)
-- | Extract the local name.
getLName :: LName -> T.Text
getLName (LName l) = l
{-|
A qualified name, consisting of a namespace URI
and the local part of the identifier, which can be empty.
The serialisation of a QName is formed by concatanating the
two components.
> Prelude> :set prompt "swish> "
> swish> :set -XOverloadedStrings
> swish> :m + Swish.QName
> swish> let qn1 = "http://example.com/" :: QName
> swish> let qn2 = "http://example.com/bob" :: QName
> swish> let qn3 = "http://example.com/bob/fred" :: QName
> swish> let qn4 = "http://example.com/bob/fred#x" :: QName
> swish> let qn5 = "http://example.com/bob/fred:joe" :: QName
> swish> map getLocalName [qn1, qn2, qn3, qn4, qn5]
> ["","bob","fred","x","fred:joe"]
> swish> getNamespace qn1
> http://example.com/
> swish> getNamespace qn2
> http://example.com/
> swish> getNamespace qn3
> http://example.com/bob/
> swish> getNamespace qn4
> http://example.com/bob/fred#
-}
{-
For now I have added in storing the actual URI
as well as the namespace component. This may or
may not be a good idea (space vs time saving).
-}
data QName = QName !InternedURI URI LName
-- | This is not total since it will fail if the input string is not a valid URI.
instance IsString QName where
fromString s =
fromMaybe (error ("QName conversion given an invalid URI: " ++ s))
(parseURIReference s >>= qnameFromURI)
-- | Equality is determined by a case sensitive comparison of the
-- URI.
instance Eq QName where
u1 == u2 = getQNameURI u1 == getQNameURI u2
-- | In @0.8.0.0@ the ordering now uses the ordering defined in
-- "Network.URI.Ord" rather than the @Show@
-- instance. This should make no difference unless a password
-- was included in the URI when using basic access authorization.
--
instance Ord QName where
compare = comparing getQNameURI
-- | The format used to display the URI is @\@, and does not
-- include the password if using basic access authorization.
instance Show QName where
show (QName u _ _) = "<" ++ show u ++ ">"
{-
The assumption in QName is that the validation done in creating
the local name is sufficient to ensure that the combined
URI is syntactically valid. Is this true?
-}
-- | Create a new qualified name with an explicit local component.
--
newQName ::
URI -- ^ Namespace
-> LName -- ^ Local component
-> QName
newQName ns l@(LName local) =
-- Until profiling shows that this is a time/space issue, we use
-- the following code rather than trying to deconstruct the URI
-- directly
let lstr = T.unpack local
uristr = show ns ++ lstr
in case parseURIReference uristr of
Just uri -> QName (intern uri) ns l
_ -> error $ "Unable to combine " ++ show ns ++ " with " ++ lstr
{-
old behavior
splitQname "http://example.org/aaa#bbb" = ("http://example.org/aaa#","bbb")
splitQname "http://example.org/aaa/bbb" = ("http://example.org/aaa/","bbb")
splitQname "http://example.org/aaa/" = ("http://example.org/aaa/","")
Should "urn:foo:bar" have a local name of "" or "foo:bar"? For now go
with the first option.
-}
-- | Create a new qualified name.
qnameFromURI ::
URI -- ^ The URI will be deconstructed to find if it contains a local component.
-> Maybe QName -- ^ The failure case may be removed.
qnameFromURI uri =
let uf = uriFragment uri
up = uriPath uri
q0 = Just $ start uri emptyLName
start = QName (intern uri)
in case uf of
"#" -> q0
'#':xs -> start (uri {uriFragment = "#"}) `fmap` newLName (T.pack xs)
"" -> case break (== '/') (reverse up) of
("",_) -> q0 -- path ends in / or is empty
(_,"") -> q0 -- path contains no /
(rlname,rpath) ->
start (uri {uriPath = reverse rpath}) `fmap`
newLName (T.pack (reverse rlname))
-- e -> error $ "Unexpected: uri=" ++ show uri ++ " has fragment='" ++ show e ++ "'"
_ -> Nothing
-- | Return the URI of the namespace stored in the QName.
-- This does not contain the local component.
--
getNamespace :: QName -> URI
getNamespace (QName _ ns _) = ns
-- | Return the local component of the QName.
getLocalName :: QName -> LName
getLocalName (QName _ _ l) = l
-- | Returns the full URI of the QName (ie the combination of the
-- namespace and local components).
getQNameURI :: QName -> URI
getQNameURI (QName u _ _) = unintern u
{-|
Convert a filepath to a file: URI stored in a QName. If the
input file path is relative then the current working directory is used
to convert it into an absolute path.
If the input represents a directory then it *must* end in
the directory separator - so for Posix systems use
@\"\/foo\/bar\/\"@ rather than
@\"\/foo\/bar\"@.
This has not been tested on Windows.
-}
{-
NOTE: not sure why I say directories should end in the path
seperator since
ghci> System.Directory.canonicalizePath "/Users/dburke/haskell/swish-text"
"/Users/dburke/haskell/swish-text"
ghci> System.Directory.canonicalizePath "/Users/dburke/haskell/swish-text/"
"/Users/dburke/haskell/swish-text"
-}
qnameFromFilePath :: FilePath -> IO QName
qnameFromFilePath fname = do
ipath <- canonicalizePath fname
let (dname, lname) = splitFileName ipath
nsuri = URI "file:" emptyAuth dname "" ""
uri = URI "file:" emptyAuth ipath "" ""
case lname of
"" -> return $ QName (intern nsuri) nsuri emptyLName
_ -> return $ QName (intern uri) nsuri (LName (T.pack lname))
emptyAuth :: Maybe URIAuth
emptyAuth = Just $ URIAuth "" "" ""
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012, 2013, 2020, 2022 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/RDF.hs 0000644 0000000 0000000 00000003416 13543702315 014117 0 ustar 00 0000000 0000000 --------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : RDF
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : FlexibleInstances, MultiParamTypeClasses, OverloadedStrings
--
-- This module provides an in-memory RDF Graph (it re-exports
-- "Swish.RDF.Graph").
--
--------------------------------------------------------------------------------
module Swish.RDF (module Swish.RDF.Graph) where
import Swish.RDF.Graph
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/RDF/BuiltIn.hs 0000644 0000000 0000000 00000003651 13543702315 015466 0 ustar 00 0000000 0000000 --------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : BuiltIn
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011, 2012 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : H98
--
-- This module collects references and provides access to all of the
-- datatypes, variable binding modifiers and variable binding filters
-- built in to Swish.
--
--------------------------------------------------------------------------------
module Swish.RDF.BuiltIn
( findRDFOpenVarBindingModifier
, findRDFDatatype
, rdfRulesetMap
, allRulesets, allDatatypeRulesets
)
where
import Swish.RDF.BuiltIn.Datatypes
import Swish.RDF.BuiltIn.Rules
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/RDF/BuiltIn/Datatypes.hs 0000644 0000000 0000000 00000005520 13543702315 017421 0 ustar 00 0000000 0000000 --------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : Datatypes
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : H98
--
-- This module collects references and provides access to all of the
-- datatypes built in to Swish.
--
--------------------------------------------------------------------------------
module Swish.RDF.BuiltIn.Datatypes
( allDatatypes, findRDFDatatype )
where
import Swish.Datatype (typeName)
import Swish.Namespace (ScopedName)
import Swish.RDF.Datatype (RDFDatatype)
import Swish.RDF.Datatype.XSD.String (rdfDatatypeXsdString)
import Swish.RDF.Datatype.XSD.Integer (rdfDatatypeXsdInteger)
import Swish.RDF.Datatype.XSD.Decimal (rdfDatatypeXsdDecimal)
import qualified Data.Map as M
------------------------------------------------------------
-- Declare datatype map
------------------------------------------------------------
-- | Al the data type declarations built into Swish.
allDatatypes :: [RDFDatatype]
allDatatypes =
[ rdfDatatypeXsdString
, rdfDatatypeXsdInteger
, rdfDatatypeXsdDecimal
]
-- | Look up a data type declaration.
findRDFDatatype :: ScopedName -> Maybe RDFDatatype
findRDFDatatype nam = M.lookup nam $
M.fromList $ map (\dt -> (typeName dt, dt)) allDatatypes
------------------------------------------------------------
-- Declare datatype subtypes map
------------------------------------------------------------
{-
allDatatypeSubtypes :: [xxx]
allDatatypeSubtypes = []
-- [[[details TBD]]]
-}
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/RDF/BuiltIn/Rules.hs 0000644 0000000 0000000 00000012677 14220136201 016554 0 ustar 00 0000000 0000000 --------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : Rules
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011, 2012, 2022 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : H98
--
-- This module collects references and provides access to all of the
-- rulesets, variable binding modifiers and variable binding filters
-- built in to Swish.
--
--------------------------------------------------------------------------------
module Swish.RDF.BuiltIn.Rules
( findRDFOpenVarBindingModifier
, rdfRulesetMap
, allRulesets, allDatatypeRulesets
)
where
import Swish.Datatype (typeRules, typeMkModifiers)
import Swish.Namespace (ScopedName)
import Swish.Ruleset (getRulesetNamespace)
import Swish.VarBinding (openVbmName, nullVarBindingModify, makeVarFilterModify, varFilterEQ, varFilterNE)
import Swish.RDF.BuiltIn.Datatypes (allDatatypes)
import Swish.RDF.Ruleset (RDFRuleset, RDFRulesetMap)
import Swish.RDF.ProofContext (rulesetRDF, rulesetRDFS, rulesetRDFD)
import Swish.RDF.VarBinding
( RDFOpenVarBindingModify
, rdfVarBindingUriRef, rdfVarBindingBlank
, rdfVarBindingLiteral
, rdfVarBindingUntypedLiteral, rdfVarBindingTypedLiteral
, rdfVarBindingXMLLiteral, rdfVarBindingDatatyped
, rdfVarBindingMemberProp
)
import qualified Data.Map as M
------------------------------------------------------------
-- Declare variable binding filters list
------------------------------------------------------------
-- |List of rdfOpenVarBindingModify values for predefined filters
--
rdfVarBindingFilters :: [RDFOpenVarBindingModify]
rdfVarBindingFilters =
[ filter1 rdfVarBindingUriRef
, filter1 rdfVarBindingBlank
, filter1 rdfVarBindingLiteral
, filter1 rdfVarBindingUntypedLiteral
, filter1 rdfVarBindingTypedLiteral
, filter1 rdfVarBindingXMLLiteral
, filter1 rdfVarBindingMemberProp
, filter2 rdfVarBindingDatatyped
-- , filterN nullVarBindingModify
, filter2 varFilterEQ
, filter2 varFilterNE
]
where
-- Swish.RDF.VarBinding.openVbmName seems to require that the label
-- list not be evaluated which means that we can not replace these
-- statements by ones like
--
-- filter1 f (lb:_) = makeVarFilterModift $ f lb
--
filter1 f lbs = makeVarFilterModify $ f (head lbs)
filter2 f lbs = makeVarFilterModify $ f (head lbs) (lbs !! 1)
-- filterN f lbs = makeVarFilterModify $ f ...
------------------------------------------------------------
-- Declare variable binding modifiers map
------------------------------------------------------------
rdfVarBindingModifiers :: [RDFOpenVarBindingModify]
rdfVarBindingModifiers =
[ nullVarBindingModify
]
------------------------------------------------------------
-- Find a named built-in OpenVarBindingModifier
------------------------------------------------------------
allOpenVarBindingModify :: [RDFOpenVarBindingModify]
allOpenVarBindingModify =
rdfVarBindingFilters ++
rdfVarBindingModifiers ++
dtsVarBindingModifiers
dtsVarBindingModifiers :: [RDFOpenVarBindingModify]
-- dtsVarBindingModifiers = concatMap dtVarBindingModifiers allDatatypes
dtsVarBindingModifiers = concatMap typeMkModifiers allDatatypes
{-
dtVarBindingModifiers dtval =
map (makeRdfDtOpenVarBindingModify dtval) (tvalMod dtval)
-}
-- | Find the named open variable binding modifier.
findRDFOpenVarBindingModifier :: ScopedName -> Maybe RDFOpenVarBindingModify
findRDFOpenVarBindingModifier nam =
M.lookup nam $ M.fromList $ map (\ovbm -> (openVbmName ovbm, ovbm))
allOpenVarBindingModify
------------------------------------------------------------
-- Lookup map for built-in rulesets
------------------------------------------------------------
-- | A 'LookupMap' of 'allRulesets'.
rdfRulesetMap :: RDFRulesetMap
rdfRulesetMap = M.fromList $ map (\r -> (getRulesetNamespace r, r)) allRulesets
-- | All the rule sets known to Swish.
allRulesets :: [RDFRuleset]
allRulesets =
[ rulesetRDF
, rulesetRDFS
, rulesetRDFD
]
++ allDatatypeRulesets
-- | The data type rule sets known to Swish.
allDatatypeRulesets :: [RDFRuleset]
allDatatypeRulesets = map typeRules allDatatypes
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012, 2022 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/RDF/ClassRestrictionRule.hs 0000644 0000000 0000000 00000054334 14220136201 020233 0 ustar 00 0000000 0000000 {-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
--------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : ClassRestrictionRule
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012, 2014, 2018, 2022 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : CPP, OverloadedStrings
--
-- This module implements an inference rule based on a restruction on class
-- membership of one or more values.
--
--------------------------------------------------------------------------------
module Swish.RDF.ClassRestrictionRule
( ClassRestriction(..), ClassRestrictionFn
, makeDatatypeRestriction, makeDatatypeRestrictionFn
, makeRDFClassRestrictionRules
, makeRDFDatatypeRestrictionRules
, falseGraph, falseGraphStr
)
where
import Swish.Datatype (DatatypeVal(..), DatatypeRel(..), DatatypeRelFn)
import Swish.Namespace (Namespace, ScopedName, namespaceToBuilder)
import Swish.Rule (Rule(..), bwdCheckInference)
import Swish.VarBinding (VarBinding(..))
import Swish.RDF.Graph
( RDFLabel(..)
, getScopedName
, RDFGraph
, getArcs
, merge
, toRDFGraph, emptyRDFGraph
, Arc(..)
, resRdfType
, resRdfdMaxCardinality
)
import Swish.RDF.Datatype (RDFDatatypeVal, fromRDFLabel, toRDFLabel)
import Swish.RDF.Ruleset (RDFRule, makeRDFGraphFromN3Builder)
import Swish.RDF.Query
( rdfQueryFind
, rdfFindValSubj, rdfFindPredVal, rdfFindPredInt
, rdfFindList
)
import Swish.RDF.VarBinding (RDFVarBinding)
import Swish.RDF.Vocabulary (namespaceRDFD)
#if (!defined(__GLASGOW_HASKELL__)) || (__GLASGOW_HASKELL__ < 808)
import Control.Applicative ((<$>))
#endif
import Data.List (delete, nub, subsequences)
import Data.Maybe (fromJust, fromMaybe, mapMaybe)
import Data.Ord.Partial (minima, maxima, partCompareEq, partComparePair, partCompareListMaybe, partCompareListSubset)
#if (!defined(__GLASGOW_HASKELL__)) || (__GLASGOW_HASKELL__ < 710)
import Data.Monoid (Monoid (..))
#endif
import qualified Data.Map as M
import qualified Data.Set as S
import qualified Data.Text.Lazy.Builder as B
------------------------------------------------------------
-- Class restriction data type
------------------------------------------------------------
-- |Type of function that evaluates missing node values in a
-- restriction from those supplied.
type ClassRestrictionFn = [Maybe RDFLabel] -> Maybe [[RDFLabel]]
-- |Datatype for named class restriction
data ClassRestriction = ClassRestriction
{ crName :: ScopedName
, crFunc :: ClassRestrictionFn
}
-- | Equality of class restrictions is based on the name of the restriction.
instance Eq ClassRestriction where
cr1 == cr2 = crName cr1 == crName cr2
instance Show ClassRestriction where
show cr = "ClassRestriction:" ++ show (crName cr)
------------------------------------------------------------
-- Instantiate a class restriction from a datatype relation
------------------------------------------------------------
-- |Make a class restriction from a datatype relation.
--
-- This lifts application of the datatype relation to operate
-- on 'RDFLabel' values, which are presumed to contain appropriately
-- datatyped values.
--
makeDatatypeRestriction ::
RDFDatatypeVal vt -> DatatypeRel vt -> ClassRestriction
makeDatatypeRestriction dtv dtrel = ClassRestriction
{ crName = dtRelName dtrel
, crFunc = makeDatatypeRestrictionFn dtv (dtRelFunc dtrel)
}
-- The core logic below is something like @(map toLabels . dtrelfn . map frLabel)@
-- but the extra lifting and catMaybes are needed to get the final result
-- type in the right form.
-- |Make a class restriction function from a datatype relation function.
--
makeDatatypeRestrictionFn ::
RDFDatatypeVal vt -> DatatypeRelFn vt -> ClassRestrictionFn
makeDatatypeRestrictionFn dtv dtrelfn =
fmap (mapMaybe toLabels) . dtrelfn . map frLabel
where
frLabel Nothing = Nothing
frLabel (Just l) = fromRDFLabel dtv l
toLabels = mapM toLabel -- Maybe [RDFLabel]
toLabel = toRDFLabel dtv
------------------------------------------------------------
-- Make rules from supplied class restrictions and graph
------------------------------------------------------------
mkPrefix :: Namespace -> B.Builder
mkPrefix = namespaceToBuilder
ruleQuery :: RDFGraph
ruleQuery = makeRDFGraphFromN3Builder $
mconcat
[ mkPrefix namespaceRDFD
, " ?c a rdfd:GeneralRestriction ; "
, " rdfd:onProperties ?p ; "
, " rdfd:constraint ?r . "
]
-- | The graph
--
-- > _:a _:b .
--
-- Exported for testing.
falseGraph :: RDFGraph
falseGraph = makeRDFGraphFromN3Builder $
mkPrefix namespaceRDFD `mappend` falseGraphStr
-- | Exported for testing.
falseGraphStr :: B.Builder
falseGraphStr = "_:a rdfd:false _:b . "
-- |Make a list of class restriction rules given a list of class restriction
-- values and a graph containing one or more class restriction definitions.
--
makeRDFClassRestrictionRules :: [ClassRestriction] -> RDFGraph -> [RDFRule]
makeRDFClassRestrictionRules crs gr =
mapMaybe constructRule (queryForRules gr)
where
queryForRules = rdfQueryFind ruleQuery
constructRule = makeRestrictionRule1 crs gr
makeRestrictionRule1 ::
[ClassRestriction] -> RDFGraph -> RDFVarBinding -> Maybe RDFRule
makeRestrictionRule1 crs gr vb =
makeRestrictionRule2 rn c ps cs
where
c = fromMaybe NoNode $ vbMap vb (Var "c")
p = fromMaybe NoNode $ vbMap vb (Var "p")
r = fromMaybe NoNode $ vbMap vb (Var "r")
cs = filter (> 0) $ map fromInteger $
rdfFindPredInt c resRdfdMaxCardinality gr
ps = rdfFindList gr p
-- TODO: do not need to go via a map since looking through a list
rn = M.lookup (getScopedName r) $ M.fromList $ map (\cr -> (crName cr, cr)) crs
makeRestrictionRule2 ::
Maybe ClassRestriction -> RDFLabel -> [RDFLabel] -> [Int]
-> Maybe RDFRule
makeRestrictionRule2 (Just restriction) cls@(Res cname) props cs =
Just restrictionRule
where
restrictionRule = Rule
{ ruleName = cname
-- fwdApply :: [ex] -> [ex]
, fwdApply = fwdApplyRestriction restriction cls props cs
-- bwdApply :: ex -> [[ex]]
, bwdApply = bwdApplyRestriction restriction cls props cs
, checkInference = bwdCheckInference restrictionRule
}
makeRestrictionRule2 _ _ _ _ = Nothing
-- trace "\nmakeRestrictionRule: missing class restriction"
-- Forward apply class restriction.
fwdApplyRestriction ::
ClassRestriction -> RDFLabel -> [RDFLabel] -> [Int] -> [RDFGraph]
-> [RDFGraph]
fwdApplyRestriction restriction cls props cs antgrs =
maybe [falseGraph] concat newgrs
where
-- Instances of the named class in the graph:
ris = nub $ rdfFindValSubj resRdfType cls antgr
-- Merge antecedent graphs into one (with bnode renaming):
-- (Uses 'if' and 'foldl1' to avoid merging in the common case
-- of just one graph supplied.)
antgr = if null antgrs then emptyRDFGraph else foldl1 merge antgrs
-- Apply class restriction to single instance of the restricted class
newgr :: RDFLabel -> Maybe [RDFGraph]
newgr ri = fwdApplyRestriction1 restriction ri props cs antgr
newgrs :: Maybe [[RDFGraph]]
newgrs = mapM newgr ris
-- Forward apply class restriction to single class instance (ci).
-- Return single set of inferred results, for each combination of
-- property values, or an empty list, or Nothing if the supplied values
-- are inconsistent with the restriction.
fwdApplyRestriction1 ::
ClassRestriction -> RDFLabel -> [RDFLabel] -> [Int] -> RDFGraph
-> Maybe [RDFGraph]
fwdApplyRestriction1 restriction ci props cs antgr =
if grConsistent then Just newgrs else Nothing
where
-- Apply restriction to graph
(grConsistent,_,_,sts) = applyRestriction restriction ci props cs antgr
-- Select results, eliminate those with unknowns
nts :: [[RDFLabel]]
nts = mapMaybe sequence sts
-- Make new graph from results, including only newly generated arcs
newarcs = S.fromList [Arc ci p v | vs <- nts, (p,v) <- zip props vs ]
`S.difference` getArcs antgr
newgrs = if S.null newarcs then [] else [toRDFGraph newarcs]
-- Backward apply class restriction.
--
-- Returns a list of alternatives, any one of which is sufficient to
-- satisfy the given consequent.
--
bwdApplyRestriction ::
ClassRestriction -> RDFLabel -> [RDFLabel] -> [Int] -> RDFGraph
-> [[RDFGraph]]
bwdApplyRestriction restriction cls props cs congr =
fromMaybe [[falseGraph]] newgrs
where
-- Instances of the named class in the graph:
ris = rdfFindValSubj resRdfType cls congr
-- Apply class restriction to single instance of the restricted class
newgr :: RDFLabel -> Maybe [[RDFGraph]]
newgr ri = bwdApplyRestriction1 restriction cls ri props cs congr
-- 'map newgr ris' is conjunction of disjunctions, where
-- each disjunction is itself a conjunction of conjunctions.
-- 'sequence' distributes the conjunction over the disjunction,
-- yielding an equivalent disjunction of conjunctions
-- map concat flattens the conjunctions of conjuctions
newgrs :: Maybe [[RDFGraph]]
newgrs = map concat . sequence <$> mapM newgr ris
-- Backward apply a class restriction to single class instance (ci).
-- Return one or more sets of antecedent results from which the consequence
-- can be derived in the defined relation, an empty list if the supplied
-- consequence cannot be inferred, or Nothing if the consequence is
-- inconsistent with the restriction.
bwdApplyRestriction1 ::
ClassRestriction -> RDFLabel -> RDFLabel -> [RDFLabel] -> [Int] -> RDFGraph
-> Maybe [[RDFGraph]]
bwdApplyRestriction1 restriction cls ci props cs congr =
if grConsistent then Just grss else Nothing
where
-- Apply restriction to graph
(grConsistent,pvs,cts,_) =
applyRestriction restriction ci props cs congr
-- Build list of all full tuples consistent with the values supplied
fts :: [[RDFLabel]]
fts = concatMap snd cts
-- Construct partial tuples from members of fts from which at least
-- one of the supplied values can be derived
pts :: [([Maybe RDFLabel],[RDFLabel])]
pts = concatMap (deriveTuple restriction) fts
-- Select combinations of members of pts from which all the
-- supplied values can be derived
dtss :: [[[Maybe RDFLabel]]]
dtss = coverSets pvs pts
-- Filter members of dtss that fully cover the values
-- obtained from the consequence graph.
ftss :: [[[Maybe RDFLabel]]]
ftss = filter (not . (\t -> coversVals deleteMaybe t pvs)) dtss
-- Make new graphs for all alternatives
grss :: [[RDFGraph]]
grss = map ( makeGraphs . newArcs ) ftss
-- Collect arcs for one alternative
newArcs dts =
[ Arc ci p v | mvs <- dts, (p,Just v) <- zip props mvs ]
-- Make graphs for one alternative
-- TODO: convert to sets
makeGraphs = map (toRDFGraph . S.fromList . (:[])) . (Arc ci resRdfType cls :)
-- Helper function to select sub-tuples from which some of a set of
-- values can be derived using a class restriction.
--
-- restriction is the restriction being evaluated.
-- ft is a full tuple of values known to be consistent with
-- the restriction
--
-- The result returned is a list of pairs, whose first member is a partial
-- tuples from which the full tuple supplied can be derived, and the second
-- is the supplied tuple calculated from that input.
--
deriveTuple ::
ClassRestriction -> [RDFLabel]
-> [([Maybe RDFLabel], [RDFLabel])]
deriveTuple restriction ft =
map (tosnd ft) $ minima partCompareListMaybe $ filter derives partials
where
partials = mapM (\x -> [Nothing, Just x]) ft
derives = ([ft] ==) . fromJust . crFunc restriction
tosnd = flip (,)
-- Helper function to apply a restriction to selected information from
-- a supplied graph, and returns a tuple containing:
-- (a) an indication of whether the graph is consistent with the
-- restriction
-- (b) a list of values specified in the graph for each property
-- (c) a complete list of tuples that use combinations of values from
-- the graph and are consistent with the restriction.
-- Each member is a pair consisting of some combination of input
-- values, and a list of complete tuple values that can be
-- calculated from those inputs, or an empty list if there is
-- insufficient information.
-- (d) a set of tuples that are consistent with the restriction and use
-- as much information from the graph as possible. This set is
-- minimal in the sense that they must all correspond to different
-- complete input tuples satisfying the restriction.
--
-- This function factors out logic that is common to forward and
-- backward chaining of a class restriction.
--
-- restriction is the class restriction being applied
-- ci is the identifier of a graph node to be tested
-- props is a list of properties of the graph noode whose values
-- are constrained by the class restriction.
-- cs is a list of max cardinality constraints on the restriction,
-- the minimum of which is used as the cardinality constraint
-- on the restriction. If the list is null, no cardinality
-- constraint is applied.
-- gr is the graph from which property values are extracted.
--
applyRestriction ::
ClassRestriction -> RDFLabel -> [RDFLabel] -> [Int] -> RDFGraph
-> ( Bool
, [[RDFLabel]]
, [([Maybe RDFLabel],[[RDFLabel]])]
, [[Maybe RDFLabel]]
)
applyRestriction restriction ci props cs gr =
(coversVals deleteMaybe sts pvs && cardinalityOK, pvs, cts, sts )
where
-- Extract from the antecedent graph all specified values of the
-- restricted properties (constructs inner list for each property)
pvs :: [[RDFLabel]]
pvs = [ rdfFindPredVal ci p gr | p <- props ]
-- Convert tuple of alternatives to list of alternative tuples
-- (Each tuple is an inner list)
pts :: [[Maybe RDFLabel]]
pts = mapM allJustAndNothing pvs
-- Try class restriction calculation for each tuple
-- For each, result may be:
-- Nothing (inconsistent)
-- Just [] (underspecified)
-- Just [t] (single tuple of values derived from given values)
-- Just ts (alternative tuples derived from given values)
rts :: [Maybe [[RDFLabel]]]
rts = map (crFunc restriction) pts
-- Extract list of consistent tuples of given values
cts :: [([Maybe RDFLabel],[[RDFLabel]])]
cts = mapMaybe tupleConv (zip pts rts)
-- TODO: be more idiomatic?
tupleConv :: (a, Maybe b) -> Maybe (a,b)
tupleConv (a, Just b) = Just (a,b)
tupleConv _ = Nothing
-- Build list of consistent tuples with maximum information
-- based on that supplied and available
-- mts = concatMap mostValues cts
mts = map mostOneValue cts
-- Eliminate consistent results subsumed by others.
-- This results in a mimimal possible set of consistent inputs,
-- because if any pair could be consistently unified then their
-- common subsumer would still be in the list, and both would be
-- thereby eliminated.
sts :: [[Maybe RDFLabel]]
sts = maxima partCompareListMaybe mts
-- Check the cardinality constraint
cardinalityOK = null cs || length sts <= minimum cs
-- Map a non-empty list of values to a list of Just values,
-- preceding each with a Nothing element.
--
-- Nothing corresponds to an unknown value. This logic is used
-- as part of constructing a list of alternative tuples of known
-- data values (either supplied or calculated from the class
-- restriction).
--
allJustAndNothing :: [a] -> [Maybe a]
allJustAndNothing as = Nothing:map Just as
{-
-- Get maximum information about possible tuple values from a
-- given pair of input tuple, which is known to be consistent with
-- the restriction, and calculated result tuples. Where the result
-- tuple is not exactly calculated, return the input tuple.
--
-- imvs tuple of Maybe element values, with Nothing for
-- unspecified values
-- movss Maybe list of possible fully-specified result tuples,
-- an empty list if no result tuples can be computed
-- based on the input tuple, or Nothing if the input
-- tuple is inconsistent.
--
mostValues :: ([Maybe a],[[a]]) -> [[Maybe a]]
mostValues (imvs,([])) = [imvs]
mostValues (_,movss) = map (map Just) movss
-}
-- Get maximum information about possible tuple values from a
-- given pair of input and possible result tuples, which is
-- known to be consistent with the restriction. If the result
-- tuple is not exactly calculated, return the input tuple.
--
-- This is a variant of mostValues that returns a single vector.
-- Multiple possible values are considered to be equivalent to
-- Just [], i.e. unknown result.
--
-- imvs tuple of Maybe element values, with Nothing for
-- unspecified values
-- movss Maybe list of possible fully-specified result tuples,
-- or an empty list if no result tuples can be computed
-- based on the input tuple.
--
mostOneValue :: ([Maybe a],[[a]]) -> [Maybe a]
mostOneValue (_,[movs]) = map Just movs
mostOneValue (imvs,_) = imvs
-- Helper function that returns subsets of dts that "cover" the indicated
-- values; i.e. from which all of the supplied values can be deduced
-- by the enumerated function results. The minima of all such subsets is
-- returned, as each of these corresponds to some minimum information needed
-- to deduce all of the given values.
--
-- pvs is a list of lists of values to be covered. The inner list
-- contains multiple values for each member of a tuple.
-- dts is an enumerated list of function values from some subset of
-- the tuple space to complete tuples. Each member is a pair
-- containing the partial tuple (using Nothing for unspecified
-- values) and the full tuple calculated from it.
--
-- The return value is a disjunction of conjunctions of partial tuples
-- that cover the indicated parameter values.
--
-- NOTE:
-- The result minimization is not perfect (cf. test2 below), but I believe
-- it is adequate for the practical situations I envisage, and in any
-- case will not result in incorrect values. It's significance is for
-- search-tree pruning. A perfect minimization might be achieved by
-- using a more subtle partial ordering that takes account of both subsets
-- and the partial ordering of set members in place of 'partCompareListSubset'.
--
coverSets :: (Eq a) => [[a]] -> [([Maybe a],[a])] -> [[[Maybe a]]]
coverSets pvs dts =
minima partCompareListSubset $ map (map fst) ctss
where
ctss = filter coverspvs $ tail $ subsequences cts
cts = minima (partComparePair partCompareListMaybe partCompareEq) dts
coverspvs cs = coversVals delete (map snd cs) pvs
-- Does a supplied list of tuples cover a list of possible alternative
-- values for each tuple member?
--
coversVals :: (a->[b]->[b]) -> [[a]] -> [[b]] -> Bool
coversVals dropVal ts vss =
-- all null (foldr dropUsed vss ts)
any (all null) (scanr dropUsed vss ts)
where
-- Remove single tuple values from the list of supplied values:
dropUsed [] [] = []
dropUsed (a:as) (bs:bss) = dropVal a bs : dropUsed as bss
dropUsed _ _ = error "coversVals.dropUsed: list length mismatch"
{-
-- Does a supplied list of possible alternative values for each
-- element of a tuple cover every tuple in a supplied list?
--
-- (See module spike-coverVals.hs for test cases)
--
coversAll :: ([a]->b->Bool) -> [[a]] -> [[b]] -> Bool
coversAll matchElem vss ts = all (invss vss) ts
where
-- Test if a given tuple is covered by vss
invss vss t = and $ zipWith matchElem vss t
-- Test if the value in a Maybe is contained in a list.
maybeElem :: (Eq a) => Maybe a -> [a] -> Bool
maybeElem Nothing = const True
maybeElem (Just t) = elem t
-}
-- |Delete a Maybe value from a list
deleteMaybe :: (Eq a) => Maybe a -> [a] -> [a]
deleteMaybe Nothing as = as
deleteMaybe (Just a) as = delete a as
-- | Make restriction rules from the supplied datatype and graph.
makeRDFDatatypeRestrictionRules :: RDFDatatypeVal vt -> RDFGraph -> [RDFRule]
makeRDFDatatypeRestrictionRules dtval =
makeRDFClassRestrictionRules dcrs
where
dcrs = map (makeDatatypeRestriction dtval) (tvalRel dtval)
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012, 2014, 2018, 2022 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/RDF/Datatype.hs 0000644 0000000 0000000 00000015732 13543702315 015676 0 ustar 00 0000000 0000000 --------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : Datatype
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011, 2012, 2018 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : H98
--
-- This module defines the structures used by Swish to represent and
-- manipulate RDF datatypes.
--
--------------------------------------------------------------------------------
module Swish.RDF.Datatype
( RDFDatatype
, RDFDatatypeVal
, RDFDatatypeMod
, RDFModifierFn, RDFApplyModifier
, makeRdfDtOpenVarBindingModify, makeRdfDtOpenVarBindingModifiers
, applyRDFDatatypeMod
, RDFDatatypeSub
, fromRDFLabel, toRDFLabel, makeDatatypedLiteral
)
where
import Swish.Datatype
( Datatype
, DatatypeVal(..)
, DatatypeMap(..)
, DatatypeMod(..), ModifierFn
, ApplyModifier
, DatatypeSub(..)
)
import Swish.Namespace (ScopedName)
import Swish.VarBinding (VarBindingModify(..))
import Swish.RDF.Graph
( RDFLabel(..)
, isDatatyped
, getLiteralText
, RDFGraph
)
import Swish.RDF.VarBinding (RDFVarBinding, RDFOpenVarBindingModify)
import Data.Maybe (fromMaybe, isJust, fromJust)
import qualified Data.Text as T
------------------------------------------------------------
-- Specialize datatype framework types for use with RDF
------------------------------------------------------------
-- |RDF datatype wrapper used with RDF graph values
--
type RDFDatatype = Datatype RDFGraph RDFLabel RDFLabel
-- |RDF datatype value used with RDF graph values
--
type RDFDatatypeVal vt = DatatypeVal RDFGraph vt RDFLabel RDFLabel
-- |RDF datatype modifier used with RDF graph values
--
type RDFDatatypeMod vt = DatatypeMod vt RDFLabel RDFLabel
-- |Describe a subtype/supertype relationship between a pair
-- of RDF datatypes.
--
type RDFDatatypeSub supvt subvt = DatatypeSub RDFGraph RDFLabel RDFLabel supvt subvt
-- |RDF value modifier function type
--
-- This indicates a modifier function that operates on 'RDFLabel' values.
--
type RDFModifierFn = ModifierFn RDFLabel
-- |RDF value modifier application function type
--
-- This indicates a function that applies RDFModifierFn functions.
--
type RDFApplyModifier = ApplyModifier RDFLabel RDFLabel
--------------------------------------------------------------
-- Functions for creating datatype variable binding modifiers
--------------------------------------------------------------
-- |Create an 'RDFOpenVarBindingModify' value.
--
-- The key purpose of this function is to lift the supplied
-- variable constraint functions from operating on data values directly
-- to a corresponding list of functions that operate on values contained
-- in RDF graph labels (i.e. RDF literal nodes). It also applies
-- node type checking, such that if the actual RDF nodes supplied do
-- not contain appropriate values then the variable binding is not
-- accepted.
--
makeRdfDtOpenVarBindingModify ::
RDFDatatypeVal vt
-- ^ is an 'RDFDatatype' value containing details of the datatype
-- for which a variable binding modifier is created.
-> RDFDatatypeMod vt
-- ^ is the data value modifier value that defines the calculations
-- that are used to implement a variable binding modifier.
-> RDFOpenVarBindingModify
makeRdfDtOpenVarBindingModify dtval dtmod =
dmAppf dtmod (dmName dtmod) $ map (makeRDFModifierFn dtval) (dmModf dtmod)
-- |Create all RDFOpenVarBindingModify values for a given datatype value.
-- See 'makeRdfDtOpenVarBindingModify'.
--
makeRdfDtOpenVarBindingModifiers ::
RDFDatatypeVal vt
-- ^ is an 'RDFDatatype' value containing details of the datatype
-- for which variable binding modifiers are created.
-> [RDFOpenVarBindingModify]
makeRdfDtOpenVarBindingModifiers dtval =
map (makeRdfDtOpenVarBindingModify dtval) (tvalMod dtval)
-- |Apply a datatype modifier using supplied RDF labels to a supplied
-- RDF variable binding.
--
applyRDFDatatypeMod ::
RDFDatatypeVal vt -> RDFDatatypeMod vt -> [RDFLabel] -> [RDFVarBinding]
-> [RDFVarBinding]
applyRDFDatatypeMod dtval dtmod lbs =
vbmApply (makeRdfDtOpenVarBindingModify dtval dtmod lbs)
-- |Given details of a datatype and a single value constraint function,
-- return a new constraint function that operates on 'RDFLabel' values.
--
-- The returned constraint function incorporates checks for appropriately
-- typed literal nodes, and returns similarly typed literal nodes.
--
makeRDFModifierFn ::
RDFDatatypeVal vt -> ModifierFn vt -> RDFModifierFn
makeRDFModifierFn dtval fn ivs =
let
ivals = mapM (fromRDFLabel dtval) ivs
ovals | isJust ivals = fn (fromJust ivals)
| otherwise = []
in
fromMaybe [] $ mapM (toRDFLabel dtval) ovals
------------------------------------------------------------
-- Helpers to map between datatype values and RDFLabels
------------------------------------------------------------
-- | Convert from a typed literal to a Haskell value,
-- with the possibility of failure.
fromRDFLabel ::
RDFDatatypeVal vt -> RDFLabel -> Maybe vt
fromRDFLabel dtv lab
| isDatatyped dtnam lab = mapL2V dtmap $ getLiteralText lab
| otherwise = Nothing
where
dtnam = tvalName dtv
dtmap = tvalMap dtv
-- | Convert a Haskell value to a typed literal (label),
-- with the possibility of failure.
toRDFLabel :: RDFDatatypeVal vt -> vt -> Maybe RDFLabel
toRDFLabel dtv =
fmap (makeDatatypedLiteral dtnam) . mapV2L dtmap
where
dtnam = tvalName dtv
dtmap = tvalMap dtv
-- | Create a typed literal. No conversion is made to the
-- string representation.
makeDatatypedLiteral ::
ScopedName -- ^ data type
-> T.Text -- ^ string form of the value
-> RDFLabel
makeDatatypedLiteral = flip TypedLit
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012, 2018 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/RDF/Datatype/XSD/Decimal.hs 0000644 0000000 0000000 00000040161 14220136201 017650 0 ustar 00 0000000 0000000 {-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
--------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : Decimal
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011 William Waites, 2011, 2012, 2014, 2022 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : CPP, OverloadedStrings
--
-- This module defines the structures used to represent and
-- manipulate RDF @xsd:decimal@ datatyped literals.
--
-- Note that in versions @0.6.4@ and @0.6.5@, this module was a mixture
-- of support for @xsd:decimal@ and @xsd:double@. In @0.7.0@ the module
-- has been changed to @xsd:decimal@, but this may change.
--
--------------------------------------------------------------------------------
-- NOTE: William's code is half about xsd:decimal and half xsd:double.
-- I have changed it all to xsd:decimal since the rules do not handle some
-- of the xsd:double specific conditions (e.g. NaN/Inf values). However,
-- the values are mapped to Haskell Double values, which is not a good match
-- for xsd:decimal.
module Swish.RDF.Datatype.XSD.Decimal
( rdfDatatypeXsdDecimal
, rdfDatatypeValXsdDecimal
, typeNameXsdDecimal, namespaceXsdDecimal
, axiomsXsdDecimal, rulesXsdDecimal
)
where
import Swish.Datatype
( Datatype(..)
, DatatypeVal(..)
, DatatypeRel(..), DatatypeRelPr
, altArgs
, UnaryFnTable, unaryFnApp
, BinaryFnTable, binaryFnApp
, DatatypeMod(..)
, makeVmod11inv, makeVmod11
, makeVmod21inv, makeVmod21
, makeVmod20
)
import Swish.Namespace (Namespace, ScopedName)
import Swish.Namespace (namespaceToBuilder, makeNSScopedName)
import Swish.QName (LName)
import Swish.Ruleset (makeRuleset)
import Swish.RDF.Datatype (RDFDatatype, RDFDatatypeVal, RDFDatatypeMod)
import Swish.RDF.Datatype (makeRdfDtOpenVarBindingModifiers)
import Swish.RDF.Datatype.XSD.MapDecimal (mapXsdDecimal)
import Swish.RDF.Ruleset (RDFFormula, RDFRule, RDFRuleset)
import Swish.RDF.Ruleset (makeRDFGraphFromN3Builder, makeRDFFormula)
import Swish.RDF.ClassRestrictionRule (makeRDFDatatypeRestrictionRules)
import Swish.RDF.Vocabulary
( namespaceRDF
, namespaceRDFS
, namespaceRDFD
, namespaceXSD
, namespaceXsdType
)
#if (!defined(__GLASGOW_HASKELL__)) || (__GLASGOW_HASKELL__ < 710)
import Data.Monoid (Monoid(..))
#endif
import qualified Data.Text.Lazy.Builder as B
------------------------------------------------------------
-- Misc values
------------------------------------------------------------
nameXsdDecimal :: LName
nameXsdDecimal = "decimal"
-- |Type name for @xsd:decimal@ datatype.
typeNameXsdDecimal :: ScopedName
typeNameXsdDecimal = makeNSScopedName namespaceXSD nameXsdDecimal
-- | Namespace for @xsd:decimal@ datatype functions.
namespaceXsdDecimal :: Namespace
namespaceXsdDecimal = namespaceXsdType nameXsdDecimal
-- | The RDFDatatype value for @xsd:decimal@.
rdfDatatypeXsdDecimal :: RDFDatatype
rdfDatatypeXsdDecimal = Datatype rdfDatatypeValXsdDecimal
-- |Define Datatype value for @xsd:decimal@.
--
-- Members of this datatype decimal values.
--
-- The lexical form consists of an optional @+@ or @-@
-- followed by a sequence of decimal digits, an optional
-- decimal point and a sequence of decimal digits.
--
-- The canonical lexical form has leading zeros and @+@ sign removed.
--
rdfDatatypeValXsdDecimal :: RDFDatatypeVal Double
rdfDatatypeValXsdDecimal = DatatypeVal
{ tvalName = typeNameXsdDecimal
, tvalRules = rdfRulesetXsdDecimal -- Ruleset RDFGraph
, tvalMkRules = makeRDFDatatypeRestrictionRules rdfDatatypeValXsdDecimal
-- RDFGraph -> [RDFRules]
, tvalMkMods = makeRdfDtOpenVarBindingModifiers rdfDatatypeValXsdDecimal
, tvalMap = mapXsdDecimal -- DatatypeMap Double
, tvalRel = relXsdDecimal -- [DatatypeRel Double]
, tvalMod = modXsdDecimal -- [DatatypeMod Double]
}
-- |relXsdDecimal contains arithmetic and other relations for xsd:decimal values.
--
-- The functions are inspired by those defined by CWM as math: properties
-- ().
--
relXsdDecimal :: [DatatypeRel Double]
relXsdDecimal =
[ relXsdDecimalAbs
, relXsdDecimalNeg
, relXsdDecimalSum
, relXsdDecimalDiff
, relXsdDecimalProd
, relXsdDecimalPower
, relXsdDecimalEq
, relXsdDecimalNe
, relXsdDecimalLt
, relXsdDecimalLe
, relXsdDecimalGt
, relXsdDecimalGe
]
mkDecRel2 ::
LName -> DatatypeRelPr Double -> UnaryFnTable Double
-> DatatypeRel Double
mkDecRel2 nam pr fns = DatatypeRel
{ dtRelName = makeNSScopedName namespaceXsdDecimal nam
, dtRelFunc = altArgs pr fns unaryFnApp
}
mkDecRel3 ::
LName -> DatatypeRelPr Double -> BinaryFnTable Double
-> DatatypeRel Double
mkDecRel3 nam pr fns = DatatypeRel
{ dtRelName = makeNSScopedName namespaceXsdDecimal nam
, dtRelFunc = altArgs pr fns binaryFnApp
}
relXsdDecimalAbs :: DatatypeRel Double
relXsdDecimalAbs = mkDecRel2 "abs" (const True)
[ ( (>= 0), [ (abs,1) ] )
, ( const True, [ (id,0), (negate,0) ] )
]
relXsdDecimalNeg :: DatatypeRel Double
relXsdDecimalNeg = mkDecRel2 "neg" (const True)
[ ( const True, [ (negate,1) ] )
, ( const True, [ (negate,0) ] )
]
relXsdDecimalSum :: DatatypeRel Double
relXsdDecimalSum = mkDecRel3 "sum" (const True)
[ ( const True, [ ((+),1,2) ] )
, ( const True, [ ((-),0,2) ] )
, ( const True, [ ((-),0,1) ] )
]
relXsdDecimalDiff :: DatatypeRel Double
relXsdDecimalDiff = mkDecRel3 "diff" (const True)
[ ( const True, [ ((-),1,2) ] )
, ( const True, [ ((+),0,2) ] )
, ( const True, [ ((-),1,0) ] )
]
relXsdDecimalProd :: DatatypeRel Double
relXsdDecimalProd = mkDecRel3 "prod" (const True)
[ ( const True, [ ((*),1,2) ] )
, ( const True, [ ((/),0,2) ] )
, ( const True, [ ((/),0,1) ] )
]
relXsdDecimalPower :: DatatypeRel Double
relXsdDecimalPower = mkDecRel3 "power" (const True)
[ ( const True, [ ((**),1,2) ] )
, ( const True, [ ] )
, ( (>= 0), [ ] )
]
liftL2 :: (a->a->Bool) -> ([a]->a) -> ([a]->a) -> [a] -> Bool
liftL2 p i1 i2 as = p (i1 as) (i2 as)
lcomp :: (a->a->Bool) -> [a] -> Bool
lcomp p = liftL2 p head (head . tail)
-- eq
relXsdDecimalEq :: DatatypeRel Double
relXsdDecimalEq = mkDecRel2 "eq" (lcomp (==))
( repeat (const True, []) )
-- ne
relXsdDecimalNe :: DatatypeRel Double
relXsdDecimalNe = mkDecRel2 "ne" (lcomp (/=))
( repeat (const True, []) )
-- lt
relXsdDecimalLt :: DatatypeRel Double
relXsdDecimalLt = mkDecRel2 "lt" (lcomp (<))
( repeat (const True, []) )
-- le
relXsdDecimalLe :: DatatypeRel Double
relXsdDecimalLe = mkDecRel2 "le" (lcomp (<=))
( repeat (const True, []) )
-- gt
relXsdDecimalGt :: DatatypeRel Double
relXsdDecimalGt = mkDecRel2 "gt" (lcomp (>))
( repeat (const True, []) )
-- ge
relXsdDecimalGe :: DatatypeRel Double
relXsdDecimalGe = mkDecRel2 "ge" (lcomp (>=))
( repeat (const True, []) )
-- |modXsdDecimal contains variable binding modifiers for xsd:decimal values.
--
-- The functions are selected from those defined by CWM as math:
-- properties
-- ().
--
modXsdDecimal :: [RDFDatatypeMod Double]
modXsdDecimal =
[ modXsdDecimalAbs
, modXsdDecimalNeg
, modXsdDecimalSum
, modXsdDecimalDiff
, modXsdDecimalProd
, modXsdDecimalPower
, modXsdDecimalEq
, modXsdDecimalNe
, modXsdDecimalLt
, modXsdDecimalLe
, modXsdDecimalGt
, modXsdDecimalGe
]
modXsdDecimalAbs :: RDFDatatypeMod Double
modXsdDecimalAbs = DatatypeMod
{ dmName = makeNSScopedName namespaceXsdDecimal "abs"
, dmModf = [ f0, f1 ]
, dmAppf = makeVmod11
}
where
f0 vs@[v1,v2] = if v1 == abs v2 then vs else []
f0 _ = []
f1 [v2] = [abs v2]
f1 _ = []
modXsdDecimalNeg :: RDFDatatypeMod Double
modXsdDecimalNeg = DatatypeMod
{ dmName = makeNSScopedName namespaceXsdDecimal "neg"
, dmModf = [ f0, f1, f1 ]
, dmAppf = makeVmod11inv
}
where
f0 vs@[v1,v2] = if v1 == negate v2 then vs else []
f0 _ = []
f1 [vi] = [-vi]
f1 _ = []
modXsdDecimalSum :: RDFDatatypeMod Double
modXsdDecimalSum = DatatypeMod
{ dmName = makeNSScopedName namespaceXsdDecimal "sum"
, dmModf = [ f0, f1, f2, f2 ]
, dmAppf = makeVmod21inv
}
where
f0 vs@[v1,v2,v3] = if v1 == v2 + v3 then vs else []
f0 _ = []
f1 [v2,v3] = [v2 + v3]
f1 _ = []
f2 [v1,vi] = [v1 - vi]
f2 _ = []
modXsdDecimalDiff :: RDFDatatypeMod Double
modXsdDecimalDiff = DatatypeMod
{ dmName = makeNSScopedName namespaceXsdDecimal "diff"
, dmModf = [ f0, f1, f2, f3 ]
, dmAppf = makeVmod21inv
}
where
f0 vs@[v1,v2,v3] = if v1 == v2 - v3 then vs else []
f0 _ = []
f1 [v2,v3] = [v2 - v3]
f1 _ = []
f2 [v1,v3] = [v1 + v3]
f2 _ = []
f3 [v1,v2] = [v2 - v1]
f3 _ = []
modXsdDecimalProd :: RDFDatatypeMod Double
modXsdDecimalProd = DatatypeMod
{ dmName = makeNSScopedName namespaceXsdDecimal "prod"
, dmModf = [ f0, f1, f2, f2 ]
, dmAppf = makeVmod21inv
}
where
f0 vs@[v1,v2,v3] = if v1 == v2 * v3 then vs else []
f0 _ = []
f1 [v2,v3] = [v2 * v3]
f1 _ = []
f2 [v1,vi] = [v1 / vi]
f2 _ = []
modXsdDecimalPower :: RDFDatatypeMod Double
modXsdDecimalPower = DatatypeMod
{ dmName = makeNSScopedName namespaceXsdDecimal "power"
, dmModf = [ f0, f1 ]
, dmAppf = makeVmod21
}
where
f0 vs@[v1,v2,v3] = if v1 == (v2 ** v3 :: Double) then vs else []
f0 _ = []
f1 [v2,v3] = [v2 ** v3 :: Double]
f1 _ = []
modXsdDecimalEq, modXsdDecimalNe, modXsdDecimalLt, modXsdDecimalLe, modXsdDecimalGt, modXsdDecimalGe :: RDFDatatypeMod Double
modXsdDecimalEq = modXsdDecimalCompare "eq" (==)
modXsdDecimalNe = modXsdDecimalCompare "ne" (/=)
modXsdDecimalLt = modXsdDecimalCompare "lt" (<)
modXsdDecimalLe = modXsdDecimalCompare "le" (<=)
modXsdDecimalGt = modXsdDecimalCompare "gt" (>)
modXsdDecimalGe = modXsdDecimalCompare "ge" (>=)
modXsdDecimalCompare ::
LName -> (Double->Double->Bool) -> RDFDatatypeMod Double
modXsdDecimalCompare nam rel = DatatypeMod
{ dmName = makeNSScopedName namespaceXsdDecimal nam
, dmModf = [ f0 ]
, dmAppf = makeVmod20
}
where
f0 vs@[v1,v2] = if rel v1 v2 then vs else []
f0 _ = []
-- |rulesetXsdDecimal contains rules and axioms that allow additional
-- deductions when xsd:decimal values appear in a graph.
--
-- The rules defined here are concerned with basic decimal arithmetic
-- operations: +, -, *, /, **
--
-- makeRuleset :: Namespace -> [Formula ex] -> [Rule ex] -> Ruleset ex
--
rdfRulesetXsdDecimal :: RDFRuleset
rdfRulesetXsdDecimal =
makeRuleset namespaceXsdDecimal axiomsXsdDecimal rulesXsdDecimal
prefixXsdDecimal :: B.Builder
prefixXsdDecimal =
mconcat $ map namespaceToBuilder
[ namespaceRDF
, namespaceRDFS
, namespaceRDFD
, namespaceXSD
, namespaceXsdDecimal
]
mkAxiom :: LName -> B.Builder -> RDFFormula
mkAxiom local gr =
makeRDFFormula namespaceXsdDecimal local (prefixXsdDecimal `mappend` gr)
-- | The axioms for @xsd:decimal@, which are
--
-- > xsd:decimal a rdfs:Datatype .
--
axiomsXsdDecimal :: [RDFFormula]
axiomsXsdDecimal =
[ mkAxiom "dt"
"xsd:decimal rdf:type rdfs:Datatype ."
-- "xsd:double rdf:type rdfs:Datatype ."
]
-- | The rules for @xsd:decimal@.
--
rulesXsdDecimal :: [RDFRule]
rulesXsdDecimal = makeRDFDatatypeRestrictionRules rdfDatatypeValXsdDecimal gr
where
gr = makeRDFGraphFromN3Builder rulesXsdDecimalBuilder
--- I have removed the newline which was added between each line
--- to improve the clarity of parser errors.
---
rulesXsdDecimalBuilder :: B.Builder
rulesXsdDecimalBuilder =
mconcat
[ prefixXsdDecimal
, "xsd_decimal:Abs a rdfd:GeneralRestriction ; "
, " rdfd:onProperties (rdf:_1 rdf:_2) ; "
, " rdfd:constraint xsd_decimal:abs ; "
, " rdfd:maxCardinality \"1\"^^xsd:nonNegativeInteger . "
, "xsd_decimal:Neg a rdfd:GeneralRestriction ; "
, " rdfd:onProperties (rdf:_1 rdf:_2) ; "
, " rdfd:constraint xsd_decimal:neg ; "
, " rdfd:maxCardinality \"1\"^^xsd:nonNegativeInteger . "
, "xsd_decimal:Sum a rdfd:GeneralRestriction ; "
, " rdfd:onProperties (rdf:_1 rdf:_2 rdf:_3) ; "
, " rdfd:constraint xsd_decimal:sum ; "
, " rdfd:maxCardinality \"1\"^^xsd:nonNegativeInteger . "
, "xsd_decimal:Diff a rdfd:GeneralRestriction ; "
, " rdfd:onProperties (rdf:_1 rdf:_2 rdf:_3) ; "
, " rdfd:constraint xsd_decimal:diff ; "
, " rdfd:maxCardinality \"1\"^^xsd:nonNegativeInteger . "
, "xsd_decimal:Prod a rdfd:GeneralRestriction ; "
, " rdfd:onProperties (rdf:_1 rdf:_2 rdf:_3) ; "
, " rdfd:constraint xsd_decimal:prod ; "
, " rdfd:maxCardinality \"1\"^^xsd:nonNegativeInteger . "
, "xsd_decimal:DivMod a rdfd:GeneralRestriction ; "
, " rdfd:onProperties (rdf:_1 rdf:_2 rdf:_3 rdf:_4) ; "
, " rdfd:constraint xsd_decimal:divmod ; "
, " rdfd:maxCardinality \"1\"^^xsd:nonNegativeInteger . "
, "xsd_decimal:Power a rdfd:GeneralRestriction ; "
, " rdfd:onProperties (rdf:_1 rdf:_2 rdf:_3) ; "
, " rdfd:constraint xsd_decimal:power ; "
, " rdfd:maxCardinality \"1\"^^xsd:nonNegativeInteger . "
, "xsd_decimal:Eq a rdfd:GeneralRestriction ; "
, " rdfd:onProperties (rdf:_1 rdf:_2) ; "
, " rdfd:constraint xsd_decimal:eq ; "
, " rdfd:maxCardinality \"1\"^^xsd:nonNegativeInteger . "
, "xsd_decimal:Ne a rdfd:GeneralRestriction ; "
, " rdfd:onProperties (rdf:_1 rdf:_2) ; "
, " rdfd:constraint xsd_decimal:ne ; "
, " rdfd:maxCardinality \"1\"^^xsd:nonNegativeInteger . "
, "xsd_decimal:Lt a rdfd:GeneralRestriction ; "
, " rdfd:onProperties (rdf:_1 rdf:_2) ; "
, " rdfd:constraint xsd_decimal:lt ; "
, " rdfd:maxCardinality \"1\"^^xsd:nonNegativeInteger . "
, "xsd_decimal:Le a rdfd:GeneralRestriction ; "
, " rdfd:onProperties (rdf:_1 rdf:_2) ; "
, " rdfd:constraint xsd_decimal:le ; "
, " rdfd:maxCardinality \"1\"^^xsd:nonNegativeInteger . "
, "xsd_decimal:Gt a rdfd:GeneralRestriction ; "
, " rdfd:onProperties (rdf:_1 rdf:_2) ; "
, " rdfd:constraint xsd_decimal:gt ; "
, " rdfd:maxCardinality \"1\"^^xsd:nonNegativeInteger . "
, "xsd_decimal:Ge a rdfd:GeneralRestriction ; "
, " rdfd:onProperties (rdf:_1 rdf:_2) ; "
, " rdfd:constraint xsd_decimal:ge ; "
, " rdfd:maxCardinality \"1\"^^xsd:nonNegativeInteger . "
]
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011 William Waites, 2011, 2012, 2014, 2022 Douglas Burke,
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/RDF/Datatype/XSD/Integer.hs 0000644 0000000 0000000 00000042521 14220136201 017711 0 ustar 00 0000000 0000000 {-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
--------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : Integer
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011, 2012, 2014, 2018, 2022 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : CPP, OverloadedStrings
--
-- This module defines the structures used to represent and
-- manipulate RDF @xsd:integer@ datatyped literals.
--
--------------------------------------------------------------------------------
module Swish.RDF.Datatype.XSD.Integer
( rdfDatatypeXsdInteger
, rdfDatatypeValXsdInteger
, typeNameXsdInteger, namespaceXsdInteger
, axiomsXsdInteger, rulesXsdInteger
)
where
import Swish.Datatype
( Datatype(..)
, DatatypeVal(..)
, DatatypeRel(..), DatatypeRelPr
, altArgs
, UnaryFnTable, unaryFnApp
, BinaryFnTable, binaryFnApp
, BinMaybeFnTable, binMaybeFnApp
, DatatypeMod(..)
, makeVmod11inv, makeVmod11
, makeVmod21inv, makeVmod21
, makeVmod20
, makeVmod22
)
import Swish.Namespace (Namespace, ScopedName)
import Swish.Namespace (namespaceToBuilder, makeNSScopedName)
import Swish.QName (LName)
import Swish.Ruleset (makeRuleset)
import Swish.RDF.Datatype (RDFDatatype, RDFDatatypeVal, RDFDatatypeMod)
import Swish.RDF.Datatype (makeRdfDtOpenVarBindingModifiers)
import Swish.RDF.Datatype.XSD.MapInteger (mapXsdInteger)
import Swish.RDF.Ruleset (RDFFormula, RDFRule, RDFRuleset)
import Swish.RDF.Ruleset (makeRDFGraphFromN3Builder, makeRDFFormula)
import Swish.RDF.ClassRestrictionRule (makeRDFDatatypeRestrictionRules)
import Swish.RDF.Vocabulary
( namespaceRDF
, namespaceRDFS
, namespaceRDFD
, namespaceXSD
, namespaceXsdType
)
import Data.Maybe (maybeToList)
#if (!defined(__GLASGOW_HASKELL__)) || (__GLASGOW_HASKELL__ < 710)
import Data.Monoid (Monoid(..))
#endif
import qualified Data.Text.Lazy.Builder as B
------------------------------------------------------------
-- Misc values
------------------------------------------------------------
-- Local name for Integer datatype
nameXsdInteger :: LName
nameXsdInteger = "integer"
-- |Type name for @xsd:integer@ datatype.
typeNameXsdInteger :: ScopedName
typeNameXsdInteger = makeNSScopedName namespaceXSD nameXsdInteger
-- |Namespace for @xsd:integer@ datatype functions.
namespaceXsdInteger :: Namespace
namespaceXsdInteger = namespaceXsdType nameXsdInteger
-- | The RDFDatatype value for @xsd:integer@.
rdfDatatypeXsdInteger :: RDFDatatype
rdfDatatypeXsdInteger = Datatype rdfDatatypeValXsdInteger
-- Integer power (exponentiation) function
-- returns Nothing if exponent is negative.
--
intPower :: Integer -> Integer -> Maybe Integer
intPower a b = if b < 0 then Nothing else Just (intPower1 a b)
where
intPower1 x y
| q == 1 = atopsq*x
| p == 0 = 1
| otherwise = atopsq
where
(p,q) = y `divMod` 2
atop = intPower1 x p
atopsq = atop*atop
------------------------------------------------------------
-- Implmentation of RDFDatatypeVal for xsd:integer
------------------------------------------------------------
-- |Define Datatype value for @xsd:integer@.
--
-- Members of this datatype are positive or negative integer values.
--
-- The lexical form consists of an optional @+@ or @-@
-- followed by a sequence of decimal digits.
--
-- The canonical lexical form has leading zeros and @+@ sign removed.
--
rdfDatatypeValXsdInteger :: RDFDatatypeVal Integer
rdfDatatypeValXsdInteger = DatatypeVal
{ tvalName = typeNameXsdInteger
, tvalRules = rdfRulesetXsdInteger -- Ruleset RDFGraph
, tvalMkRules = makeRDFDatatypeRestrictionRules rdfDatatypeValXsdInteger
-- RDFGraph -> [RDFRules]
, tvalMkMods = makeRdfDtOpenVarBindingModifiers rdfDatatypeValXsdInteger
, tvalMap = mapXsdInteger -- DatatypeMap Integer
, tvalRel = relXsdInteger -- [DatatypeRel Integer]
, tvalMod = modXsdInteger -- [DatatypeMod Integer]
}
-- |relXsdInteger contains arithmetic and other relations for xsd:Integer values.
--
-- The functions are inspired by those defined by CWM as math: properties
-- ().
--
relXsdInteger :: [DatatypeRel Integer]
relXsdInteger =
[ relXsdIntegerAbs
, relXsdIntegerNeg
, relXsdIntegerSum
, relXsdIntegerDiff
, relXsdIntegerProd
, relXsdIntegerDivMod
, relXsdIntegerPower
, relXsdIntegerEq
, relXsdIntegerNe
, relXsdIntegerLt
, relXsdIntegerLe
, relXsdIntegerGt
, relXsdIntegerGe
]
mkIntRel2 ::
LName -> DatatypeRelPr Integer -> UnaryFnTable Integer
-> DatatypeRel Integer
mkIntRel2 nam pr fns = DatatypeRel
{ dtRelName = makeNSScopedName namespaceXsdInteger nam
, dtRelFunc = altArgs pr fns unaryFnApp
}
mkIntRel3 ::
LName -> DatatypeRelPr Integer -> BinaryFnTable Integer
-> DatatypeRel Integer
mkIntRel3 nam pr fns = DatatypeRel
{ dtRelName = makeNSScopedName namespaceXsdInteger nam
, dtRelFunc = altArgs pr fns binaryFnApp
}
mkIntRel3maybe ::
LName -> DatatypeRelPr Integer -> BinMaybeFnTable Integer
-> DatatypeRel Integer
mkIntRel3maybe nam pr fns = DatatypeRel
{ dtRelName = makeNSScopedName namespaceXsdInteger nam
, dtRelFunc = altArgs pr fns binMaybeFnApp
}
relXsdIntegerAbs :: DatatypeRel Integer
relXsdIntegerAbs = mkIntRel2 "abs" (const True)
[ ( (>= 0), [ (abs,1) ] )
, ( const True, [ (id,0), (negate,0) ] )
]
relXsdIntegerNeg :: DatatypeRel Integer
relXsdIntegerNeg = mkIntRel2 "neg" (const True)
[ ( const True, [ (negate,1) ] )
, ( const True, [ (negate,0) ] )
]
relXsdIntegerSum :: DatatypeRel Integer
relXsdIntegerSum = mkIntRel3 "sum" (const True)
[ ( const True, [ ((+),1,2) ] )
, ( const True, [ ((-),0,2) ] )
, ( const True, [ ((-),0,1) ] )
]
relXsdIntegerDiff :: DatatypeRel Integer
relXsdIntegerDiff = mkIntRel3 "diff" (const True)
[ ( const True, [ ((-),1,2) ] )
, ( const True, [ ((+),0,2) ] )
, ( const True, [ ((-),1,0) ] )
]
relXsdIntegerProd :: DatatypeRel Integer
relXsdIntegerProd = mkIntRel3 "prod" (const True)
[ ( const True, [ ((*),1,2) ] )
, ( const True, [ (div,0,2) ] )
, ( const True, [ (div,0,1) ] )
]
relXsdIntegerDivMod :: DatatypeRel Integer
relXsdIntegerDivMod = mkIntRel3 "divmod" (const True)
[ ( const True, [ (div,2,3) ] )
, ( const True, [ (mod,2,3) ] )
, ( const True, [ ] )
, ( const True, [ ] )
]
-- Compose with function of two arguments
c2 :: (b -> c) -> (a -> d -> b) -> a -> d -> c
c2 = (.) . (.)
relXsdIntegerPower :: DatatypeRel Integer
relXsdIntegerPower = mkIntRel3maybe "power" (const True)
[ ( const True, [ (fmap (:[]) `c2` intPower,1,2) ] )
, ( const True, [ ] )
, ( (>= 0), [ ] )
]
liftL2 :: (a->a->Bool) -> ([a]->a) -> ([a]->a) -> [a] -> Bool
liftL2 p i1 i2 as = p (i1 as) (i2 as)
lcomp :: (a->a->Bool) -> [a] -> Bool
lcomp p = liftL2 p head (head . tail)
-- eq
relXsdIntegerEq :: DatatypeRel Integer
relXsdIntegerEq = mkIntRel2 "eq" (lcomp (==))
( repeat (const True, []) )
-- ne
relXsdIntegerNe :: DatatypeRel Integer
relXsdIntegerNe = mkIntRel2 "ne" (lcomp (/=))
( repeat (const True, []) )
-- lt
relXsdIntegerLt :: DatatypeRel Integer
relXsdIntegerLt = mkIntRel2 "lt" (lcomp (<))
( repeat (const True, []) )
-- le
relXsdIntegerLe :: DatatypeRel Integer
relXsdIntegerLe = mkIntRel2 "le" (lcomp (<=))
( repeat (const True, []) )
-- gt
relXsdIntegerGt :: DatatypeRel Integer
relXsdIntegerGt = mkIntRel2 "gt" (lcomp (>))
( repeat (const True, []) )
-- ge
relXsdIntegerGe :: DatatypeRel Integer
relXsdIntegerGe = mkIntRel2 "ge" (lcomp (>=))
( repeat (const True, []) )
-- |modXsdInteger contains variable binding modifiers for xsd:Integer values.
--
-- The functions are selected from those defined by CWM as math:
-- properties
-- ().
--
modXsdInteger :: [RDFDatatypeMod Integer]
modXsdInteger =
[ modXsdIntegerAbs
, modXsdIntegerNeg
, modXsdIntegerSum
, modXsdIntegerDiff
, modXsdIntegerProd
, modXsdIntegerDivMod
, modXsdIntegerPower
, modXsdIntegerEq
, modXsdIntegerNe
, modXsdIntegerLt
, modXsdIntegerLe
, modXsdIntegerGt
, modXsdIntegerGe
]
modXsdIntegerAbs :: RDFDatatypeMod Integer
modXsdIntegerAbs = DatatypeMod
{ dmName = makeNSScopedName namespaceXsdInteger "abs"
, dmModf = [ f0, f1 ]
, dmAppf = makeVmod11
}
where
f0 vs@[v1,v2] = if v1 == abs v2 then vs else []
f0 _ = []
f1 [v2] = [abs v2]
f1 _ = []
modXsdIntegerNeg :: RDFDatatypeMod Integer
modXsdIntegerNeg = DatatypeMod
{ dmName = makeNSScopedName namespaceXsdInteger "neg"
, dmModf = [ f0, f1, f1 ]
, dmAppf = makeVmod11inv
}
where
f0 vs@[v1,v2] = if v1 == negate v2 then vs else []
f0 _ = []
f1 [vi] = [-vi]
f1 _ = []
modXsdIntegerSum :: RDFDatatypeMod Integer
modXsdIntegerSum = DatatypeMod
{ dmName = makeNSScopedName namespaceXsdInteger "sum"
, dmModf = [ f0, f1, f2, f2 ]
, dmAppf = makeVmod21inv
}
where
f0 vs@[v1,v2,v3] = if v1 == v2 + v3 then vs else []
f0 _ = []
f1 [v2,v3] = [v2 + v3]
f1 _ = []
f2 [v1,vi] = [v1 - vi]
f2 _ = []
modXsdIntegerDiff :: RDFDatatypeMod Integer
modXsdIntegerDiff = DatatypeMod
{ dmName = makeNSScopedName namespaceXsdInteger "diff"
, dmModf = [ f0, f1, f2, f3 ]
, dmAppf = makeVmod21inv
}
where
f0 vs@[v1,v2,v3] = if v1 == v2 - v3 then vs else []
f0 _ = []
f1 [v2,v3] = [v2 - v3]
f1 _ = []
f2 [v1,v3] = [v1 + v3]
f2 _ = []
f3 [v1,v2] = [v2 - v1]
f3 _ = []
modXsdIntegerProd :: RDFDatatypeMod Integer
modXsdIntegerProd = DatatypeMod
{ dmName = makeNSScopedName namespaceXsdInteger "prod"
, dmModf = [ f0, f1, f2, f2 ]
, dmAppf = makeVmod21inv
}
where
f0 vs@[v1,v2,v3] = if v1 == v2 * v3 then vs else []
f0 _ = []
f1 [v2,v3] = [v2 * v3]
f1 _ = []
f2 [v1,vi] = [q | r == 0]
where (q,r) = quotRem v1 vi
f2 _ = []
modXsdIntegerDivMod :: RDFDatatypeMod Integer
modXsdIntegerDivMod = DatatypeMod
{ dmName = makeNSScopedName namespaceXsdInteger "divmod"
, dmModf = [ f0, f1 ]
, dmAppf = makeVmod22
}
where
f0 vs@[v1,v2,v3,v4] = if (v1,v2) == divMod v3 v4 then vs else []
f0 _ = []
f1 [v3,v4] = [v1,v2] where (v1,v2) = divMod v3 v4
f1 _ = []
modXsdIntegerPower :: RDFDatatypeMod Integer
modXsdIntegerPower = DatatypeMod
{ dmName = makeNSScopedName namespaceXsdInteger "power"
, dmModf = [ f0, f1 ]
, dmAppf = makeVmod21
}
where
f0 vs@[v1,v2,v3] = if Just v1 == intPower v2 v3 then vs else []
f0 _ = []
f1 [v2,v3] = maybeToList (intPower v2 v3)
f1 _ = []
modXsdIntegerEq, modXsdIntegerNe, modXsdIntegerLt, modXsdIntegerLe, modXsdIntegerGt, modXsdIntegerGe :: RDFDatatypeMod Integer
modXsdIntegerEq = modXsdIntegerCompare "eq" (==)
modXsdIntegerNe = modXsdIntegerCompare "ne" (/=)
modXsdIntegerLt = modXsdIntegerCompare "lt" (<)
modXsdIntegerLe = modXsdIntegerCompare "le" (<=)
modXsdIntegerGt = modXsdIntegerCompare "gt" (>)
modXsdIntegerGe = modXsdIntegerCompare "ge" (>=)
modXsdIntegerCompare ::
LName -> (Integer->Integer->Bool) -> RDFDatatypeMod Integer
modXsdIntegerCompare nam rel = DatatypeMod
{ dmName = makeNSScopedName namespaceXsdInteger nam
, dmModf = [ f0 ]
, dmAppf = makeVmod20
}
where
f0 vs@[v1,v2] = if rel v1 v2 then vs else []
f0 _ = []
-- |rulesetXsdInteger contains rules and axioms that allow additional
-- deductions when xsd:integer values appear in a graph.
--
-- The rules defined here are concerned with basic integer arithmetic
-- operations: +, -, *, div, rem
--
-- makeRuleset :: Namespace -> [Formula ex] -> [Rule ex] -> Ruleset ex
--
rdfRulesetXsdInteger :: RDFRuleset
rdfRulesetXsdInteger =
makeRuleset namespaceXsdInteger axiomsXsdInteger rulesXsdInteger
prefixXsdInteger :: B.Builder
prefixXsdInteger =
mconcat $ map namespaceToBuilder
[ namespaceRDF
, namespaceRDFS
, namespaceRDFD
, namespaceXSD
, namespaceXsdInteger
]
mkAxiom :: LName -> B.Builder -> RDFFormula
mkAxiom local gr =
makeRDFFormula namespaceXsdInteger local (prefixXsdInteger `mappend` gr)
-- | The axioms for @xsd:integer@, which are
--
-- > xsd:integer a rdfs:Datatype .
--
axiomsXsdInteger :: [RDFFormula]
axiomsXsdInteger =
[ mkAxiom "dt" "xsd:integer rdf:type rdfs:Datatype ."
]
-- | The rules for @xsd:integer@.
rulesXsdInteger :: [RDFRule]
rulesXsdInteger = makeRDFDatatypeRestrictionRules rdfDatatypeValXsdInteger gr
where
gr = makeRDFGraphFromN3Builder rulesXsdIntegerBuilder
--- I have removed the newline which was added between each line
--- to improve the clarity of parser errors.
---
rulesXsdIntegerBuilder :: B.Builder
rulesXsdIntegerBuilder =
mconcat
[ prefixXsdInteger
, "xsd_integer:Abs a rdfd:GeneralRestriction ; "
, " rdfd:onProperties (rdf:_1 rdf:_2) ; "
, " rdfd:constraint xsd_integer:abs ; "
, " rdfd:maxCardinality \"1\"^^xsd:nonNegativeInteger . "
, "xsd_integer:Neg a rdfd:GeneralRestriction ; "
, " rdfd:onProperties (rdf:_1 rdf:_2) ; "
, " rdfd:constraint xsd_integer:neg ; "
, " rdfd:maxCardinality \"1\"^^xsd:nonNegativeInteger . "
, "xsd_integer:Sum a rdfd:GeneralRestriction ; "
, " rdfd:onProperties (rdf:_1 rdf:_2 rdf:_3) ; "
, " rdfd:constraint xsd_integer:sum ; "
, " rdfd:maxCardinality \"1\"^^xsd:nonNegativeInteger . "
, "xsd_integer:Diff a rdfd:GeneralRestriction ; "
, " rdfd:onProperties (rdf:_1 rdf:_2 rdf:_3) ; "
, " rdfd:constraint xsd_integer:diff ; "
, " rdfd:maxCardinality \"1\"^^xsd:nonNegativeInteger . "
, "xsd_integer:Prod a rdfd:GeneralRestriction ; "
, " rdfd:onProperties (rdf:_1 rdf:_2 rdf:_3) ; "
, " rdfd:constraint xsd_integer:prod ; "
, " rdfd:maxCardinality \"1\"^^xsd:nonNegativeInteger . "
, "xsd_integer:DivMod a rdfd:GeneralRestriction ; "
, " rdfd:onProperties (rdf:_1 rdf:_2 rdf:_3 rdf:_4) ; "
, " rdfd:constraint xsd_integer:divmod ; "
, " rdfd:maxCardinality \"1\"^^xsd:nonNegativeInteger . "
, "xsd_integer:Power a rdfd:GeneralRestriction ; "
, " rdfd:onProperties (rdf:_1 rdf:_2 rdf:_3) ; "
, " rdfd:constraint xsd_integer:power ; "
, " rdfd:maxCardinality \"1\"^^xsd:nonNegativeInteger . "
, "xsd_integer:Eq a rdfd:GeneralRestriction ; "
, " rdfd:onProperties (rdf:_1 rdf:_2) ; "
, " rdfd:constraint xsd_integer:eq ; "
, " rdfd:maxCardinality \"1\"^^xsd:nonNegativeInteger . "
, "xsd_integer:Ne a rdfd:GeneralRestriction ; "
, " rdfd:onProperties (rdf:_1 rdf:_2) ; "
, " rdfd:constraint xsd_integer:ne ; "
, " rdfd:maxCardinality \"1\"^^xsd:nonNegativeInteger . "
, "xsd_integer:Lt a rdfd:GeneralRestriction ; "
, " rdfd:onProperties (rdf:_1 rdf:_2) ; "
, " rdfd:constraint xsd_integer:lt ; "
, " rdfd:maxCardinality \"1\"^^xsd:nonNegativeInteger . "
, "xsd_integer:Le a rdfd:GeneralRestriction ; "
, " rdfd:onProperties (rdf:_1 rdf:_2) ; "
, " rdfd:constraint xsd_integer:le ; "
, " rdfd:maxCardinality \"1\"^^xsd:nonNegativeInteger . "
, "xsd_integer:Gt a rdfd:GeneralRestriction ; "
, " rdfd:onProperties (rdf:_1 rdf:_2) ; "
, " rdfd:constraint xsd_integer:gt ; "
, " rdfd:maxCardinality \"1\"^^xsd:nonNegativeInteger . "
, "xsd_integer:Ge a rdfd:GeneralRestriction ; "
, " rdfd:onProperties (rdf:_1 rdf:_2) ; "
, " rdfd:constraint xsd_integer:ge ; "
, " rdfd:maxCardinality \"1\"^^xsd:nonNegativeInteger . "
]
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012, 2014, 2018, 2022 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/RDF/Datatype/XSD/MapDecimal.hs 0000644 0000000 0000000 00000005254 13543702315 020326 0 ustar 00 0000000 0000000 {-# LANGUAGE OverloadedStrings #-}
--------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : MapDecimal
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011 William Waites, 2011, 2012 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : OverloadedStrings
--
-- This module defines the datatytpe mapping and relation values
-- used for RDF dataype @xsd:decimal@.
--
--------------------------------------------------------------------------------
module Swish.RDF.Datatype.XSD.MapDecimal (mapXsdDecimal) where
import Swish.Datatype (DatatypeMap(..))
import qualified Data.Text as T
import qualified Data.Text.Read as T
------------------------------------------------------------
-- Implementation of DatatypeMap for xsd:decimal
------------------------------------------------------------
-- | Functions that perform lexical-to-value
-- and value-to-canonical-lexical mappings for @xsd:decimal@ values.
--
mapXsdDecimal :: DatatypeMap Double
mapXsdDecimal = DatatypeMap
{ -- mapL2V :: T.Text -> Maybe Double
mapL2V = \txt -> case T.double txt of
Right (val, "") -> Just val
_ -> Nothing
-- mapV2L :: Double -> Maybe T.Text
-- TODO: for now convert via String as issues with text-format
-- (inability to use with ghci)
, mapV2L = Just . T.pack . show
}
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011 William Waites, 2011, 2012 Douglas Burke
--
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/RDF/Datatype/XSD/MapInteger.hs 0000644 0000000 0000000 00000005111 13543702315 020355 0 ustar 00 0000000 0000000 {-# LANGUAGE OverloadedStrings #-}
--------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : MapInteger
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011, 2012 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : OverloadedStrings
--
-- This module defines the datatytpe mapping and relation values
-- used for RDF dataype @xsd:integer@.
--
--------------------------------------------------------------------------------
module Swish.RDF.Datatype.XSD.MapInteger (mapXsdInteger) where
import Swish.Datatype (DatatypeMap(..))
import qualified Data.Text as T
import qualified Data.Text.Read as T
------------------------------------------------------------
-- Implementation of DatatypeMap for xsd:integer
------------------------------------------------------------
-- | Functions that perform lexical-to-value
-- and value-to-canonical-lexical mappings for @xsd:integer@ values.
--
mapXsdInteger :: DatatypeMap Integer
mapXsdInteger = DatatypeMap
{ -- mapL2V :: T.Text -> Maybe Integer
mapL2V = \txt -> case T.signed T.decimal txt of
Right (val, "") -> Just val
_ -> Nothing
-- mapV2L :: Integer -> Maybe T.Text
-- TODO: for now convert via String as issues with text-format
-- (inability to use with ghci)
, mapV2L = Just . T.pack . show
}
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/RDF/Datatype/XSD/String.hs 0000644 0000000 0000000 00000023750 13543702315 017601 0 ustar 00 0000000 0000000 {-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
--------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : String
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011, 2012, 2014 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : CPP, OverloadedStrings
--
-- This module defines the structures used to represent and
-- manipulate RDF @xsd:string@ datatyped literals.
--
--------------------------------------------------------------------------------
module Swish.RDF.Datatype.XSD.String
( rdfDatatypeXsdString
, rdfDatatypeValXsdString
, typeNameXsdString, namespaceXsdString
, axiomsXsdString, rulesXsdString
)
where
import Swish.Datatype
( Datatype(..)
, DatatypeVal(..)
, DatatypeMap(..)
, DatatypeRel(..), DatatypeRelPr
, altArgs
, UnaryFnTable, unaryFnApp
, DatatypeMod(..)
, makeVmod20
)
import Swish.Namespace (Namespace, ScopedName)
import Swish.Namespace (namespaceToBuilder, makeNSScopedName)
import Swish.QName (LName)
import Swish.Ruleset (makeRuleset)
import Swish.VarBinding (VarBinding(..), VarBindingModify(..))
import Swish.VarBinding (addVarBinding)
import Swish.RDF.ClassRestrictionRule (makeRDFDatatypeRestrictionRules)
import Swish.RDF.Datatype (RDFDatatype, RDFDatatypeVal, RDFDatatypeMod)
import Swish.RDF.Datatype (makeRdfDtOpenVarBindingModifiers )
import Swish.RDF.Graph (RDFLabel(..))
import Swish.RDF.Ruleset (RDFFormula, RDFRule, RDFRuleset)
import Swish.RDF.Ruleset (makeRDFGraphFromN3Builder, makeRDFFormula, makeN3ClosureRule)
import Swish.RDF.VarBinding (RDFVarBindingModify)
import Swish.RDF.Vocabulary
( namespaceRDF
, namespaceRDFS
, namespaceRDFD
, namespaceXSD
, namespaceXsdType
)
#if (!defined(__GLASGOW_HASKELL__)) || (__GLASGOW_HASKELL__ < 710)
import Data.Monoid(Monoid(..))
#endif
import qualified Data.Text as T
import qualified Data.Text.Lazy.Builder as B
------------------------------------------------------------
-- Misc values
------------------------------------------------------------
-- Local name for Integer datatype
nameXsdString :: LName
nameXsdString = "string"
-- | Type name for @xsd:string@ datatype
typeNameXsdString :: ScopedName
typeNameXsdString = makeNSScopedName namespaceXSD nameXsdString
-- | Namespace for @xsd:string@ datatype functions
namespaceXsdString :: Namespace
namespaceXsdString = namespaceXsdType nameXsdString
-- | The RDFDatatype value for @xsd:string@.
rdfDatatypeXsdString :: RDFDatatype
rdfDatatypeXsdString = Datatype rdfDatatypeValXsdString
------------------------------------------------------------
-- Implmentation of RDFDatatypeVal for xsd:integer
------------------------------------------------------------
-- |Define Datatype value for @xsd:string@.
--
rdfDatatypeValXsdString :: RDFDatatypeVal T.Text
rdfDatatypeValXsdString = DatatypeVal
{ tvalName = typeNameXsdString
, tvalRules = rdfRulesetXsdString
, tvalMkRules = makeRDFDatatypeRestrictionRules rdfDatatypeValXsdString
, tvalMkMods = makeRdfDtOpenVarBindingModifiers rdfDatatypeValXsdString
, tvalMap = mapXsdString
, tvalRel = relXsdString
, tvalMod = modXsdString
}
-- |mapXsdString contains functions that perform lexical-to-value
-- and value-to-canonical-lexical mappings for @xsd:string@ values
--
-- These are identity mappings.
--
mapXsdString :: DatatypeMap T.Text
mapXsdString = DatatypeMap
{ mapL2V = Just
, mapV2L = Just
}
-- |relXsdString contains useful relations for @xsd:string@ values.
--
relXsdString :: [DatatypeRel T.Text]
relXsdString =
[ relXsdStringEq
, relXsdStringNe
]
mkStrRel2 ::
LName -> DatatypeRelPr T.Text -> UnaryFnTable T.Text
-> DatatypeRel T.Text
mkStrRel2 nam pr fns =
DatatypeRel
{ dtRelName = makeNSScopedName namespaceXsdString nam
, dtRelFunc = altArgs pr fns unaryFnApp
}
{-
mkStrRel3 ::
String -> DatatypeRelPr String -> BinaryFnTable String
-> DatatypeRel String
mkStrRel3 nam pr fns = DatatypeRel
{ dtRelName = ScopedName namespaceXsdString nam
, dtRelFunc = altArgs pr fns binaryFnApp
}
mkStrRel3maybe ::
String -> DatatypeRelPr String -> BinMaybeFnTable String
-> DatatypeRel String
mkStrRel3maybe nam pr fns = DatatypeRel
{ dtRelName = ScopedName namespaceXsdString nam
, dtRelFunc = altArgs pr fns binMaybeFnApp
}
-}
liftL2 :: (a->a->Bool) -> ([a]->a) -> ([a]->a) -> [a] -> Bool
liftL2 p i1 i2 as = p (i1 as) (i2 as)
lcomp :: (a->a->Bool) -> [a] -> Bool
lcomp p = liftL2 p head (head . tail)
-- eq
relXsdStringEq :: DatatypeRel T.Text
relXsdStringEq = mkStrRel2 "eq" (lcomp (==))
( repeat (const True, []) )
-- ne
relXsdStringNe :: DatatypeRel T.Text
relXsdStringNe = mkStrRel2 "ne" (lcomp (/=))
( repeat (const True, []) )
-- |modXsdString contains variable binding modifiers for @xsd:string@ values.
--
modXsdString :: [RDFDatatypeMod T.Text]
modXsdString =
[ modXsdStringEq
, modXsdStringNe
]
modXsdStringEq, modXsdStringNe :: RDFDatatypeMod T.Text
modXsdStringEq = modXsdStringCompare "eq" (==)
modXsdStringNe = modXsdStringCompare "ne" (/=)
modXsdStringCompare ::
LName -> (T.Text->T.Text->Bool) -> RDFDatatypeMod T.Text
modXsdStringCompare nam rel = DatatypeMod
{ dmName = makeNSScopedName namespaceXsdString nam
, dmModf = [ f0 ]
, dmAppf = makeVmod20
}
where
f0 vs@[v1,v2] = if rel v1 v2 then vs else []
f0 _ = []
-- |rulesetXsdString contains rules and axioms that allow additional
-- deductions when xsd:string values appear in a graph.
--
-- makeRuleset :: Namespace -> [Formula ex] -> [Rule ex] -> Ruleset ex
--
rdfRulesetXsdString :: RDFRuleset
rdfRulesetXsdString =
makeRuleset namespaceXsdString axiomsXsdString rulesXsdString
mkPrefix :: Namespace -> B.Builder
mkPrefix = namespaceToBuilder
prefixXsdString :: B.Builder
prefixXsdString =
mconcat
[ mkPrefix namespaceRDF
, mkPrefix namespaceRDFS
, mkPrefix namespaceRDFD
, mkPrefix namespaceXSD
, mkPrefix namespaceXsdString
]
mkAxiom :: LName -> B.Builder -> RDFFormula
mkAxiom local gr =
makeRDFFormula namespaceXsdString local (prefixXsdString `mappend` gr)
-- | The axioms for @xsd:string@, which are
--
-- > xsd:string a rdfs:Datatype .
--
axiomsXsdString :: [RDFFormula]
axiomsXsdString =
[ mkAxiom "dt" "xsd:string rdf:type rdfs:Datatype ."
]
-- | The rules for @xsd:string@.
rulesXsdString :: [RDFRule]
rulesXsdString = rulesXsdStringClosure ++ rulesXsdStringRestriction
rulesXsdStringRestriction :: [RDFRule]
rulesXsdStringRestriction =
makeRDFDatatypeRestrictionRules rdfDatatypeValXsdString gr
where
gr = makeRDFGraphFromN3Builder rulesXsdStringBuilder
rulesXsdStringBuilder :: B.Builder
rulesXsdStringBuilder =
mconcat
[ prefixXsdString
, "xsd_string:Eq a rdfd:GeneralRestriction ; "
, " rdfd:onProperties (rdf:_1 rdf:_2) ; "
, " rdfd:constraint xsd_string:eq ; "
, " rdfd:maxCardinality \"1\"^^xsd:nonNegativeInteger . "
, "xsd_string:Ne a rdfd:GeneralRestriction ; "
, " rdfd:onProperties (rdf:_1 rdf:_2) ; "
, " rdfd:constraint xsd_string:ne ; "
, " rdfd:maxCardinality \"1\"^^xsd:nonNegativeInteger . "
]
rulesXsdStringClosure :: [RDFRule]
rulesXsdStringClosure =
[ xsdstrls
, xsdstrsl
]
-- Infer string from plain literal
xsdstrls :: RDFRule
xsdstrls = makeN3ClosureRule namespaceXsdString "ls"
"?a ?p ?l ."
"?a ?p ?s ."
(stringPlain "s" "l")
-- Infer plain literal from string
xsdstrsl :: RDFRule
xsdstrsl = makeN3ClosureRule namespaceXsdString "sl"
"?a ?p ?s ."
"?a ?p ?l ."
(stringPlain "s" "l")
-- Map between string and plain literal values
stringPlain :: String -> String -> RDFVarBindingModify
stringPlain svar lvar = stringPlainValue (Var svar) (Var lvar)
-- Variable binding modifier to create new binding to a canonical
-- form of a datatyped literal.
stringPlainValue ::
RDFLabel -> RDFLabel -> RDFVarBindingModify
stringPlainValue svar lvar = VarBindingModify
{ vbmName = makeNSScopedName namespaceRDFD "stringPlain"
, vbmApply = concatMap app1
, vbmVocab = [svar,lvar]
, vbmUsage = [[svar],[lvar],[]]
}
where
app1 vbind = app2 (vbMap vbind svar) (vbMap vbind lvar) vbind
-- Going to assume can only get TypedLit here, and assume LangLit
-- can be ignored.
app2 (Just (TypedLit s _))
(Just (Lit l))
vbind
| s == l
= [vbind]
app2 (Just (TypedLit s _))
Nothing
vbind
= [addVarBinding lvar (Lit s) vbind]
app2 Nothing
(Just (Lit l))
vbind
= [addVarBinding svar (TypedLit l typeNameXsdString) vbind]
app2 _ _ _ = []
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/RDF/Formatter/NTriples.hs 0000644 0000000 0000000 00000014660 13767237337 017644 0 ustar 00 0000000 0000000 {-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
--------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : NTriples
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012, 2013, 2014, 2015, 2020 Doug Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : OverloadedStrings
--
-- This Module implements a NTriples formatter for a 'RDFGraph'.
--
-- REFERENCES:
--
-- - \"RDF Test Cases\",
-- W3C Recommendation 10 February 2004,
--
--
-- NOTES:
--
-- - Update to the document \"N-Triples. A line-based syntax for an RDF graph\"
-- W3C Working Group Note 09 April 2013,
--
--
--------------------------------------------------------------------------------
module Swish.RDF.Formatter.NTriples
( formatGraphAsText
, formatGraphAsLazyText
, formatGraphAsBuilder
)
where
import Swish.RDF.Formatter.Internal ( NodeGenState(..)
, SLens (..)
, emptyNgs
, mapBlankNode_
)
import Swish.GraphClass (Arc(..))
import Swish.Namespace (ScopedName, getQName)
import Swish.RDF.Graph (RDFGraph, RDFLabel(..))
import Swish.RDF.Graph (getArcs)
import Swish.RDF.Vocabulary (fromLangTag)
import Control.Monad.State
import Data.Char (ord, intToDigit, toUpper)
#if (!defined(__GLASGOW_HASKELL__)) || (__GLASGOW_HASKELL__ < 710)
import Data.Monoid
import Control.Applicative ((<$>))
#endif
-- import Prelude
-- it strikes me that using Lazy Text here is likely to be
-- wrong; however I have done no profiling to back this
-- assumption up!
import qualified Data.Set as S
import qualified Data.Text as T
import qualified Data.Text.Lazy as L
import qualified Data.Text.Lazy.Builder as B
----------------------------------------------------------------------
-- Graph formatting state monad
----------------------------------------------------------------------
--
-- This is a lot simpler than other formatters.
type Formatter a = State NodeGenState a
_nodeGen :: SLens NodeGenState NodeGenState
_nodeGen = SLens id $ const id
-- | Convert a RDF graph to NTriples format.
formatGraphAsText :: RDFGraph -> T.Text
formatGraphAsText = L.toStrict . formatGraphAsLazyText
-- | Convert a RDF graph to NTriples format.
formatGraphAsLazyText :: RDFGraph -> L.Text
formatGraphAsLazyText = B.toLazyText . formatGraphAsBuilder
-- | Convert a RDF graph to NTriples format.
formatGraphAsBuilder :: RDFGraph -> B.Builder
formatGraphAsBuilder gr = evalState (formatGraph gr) emptyNgs
----------------------------------------------------------------------
-- Formatting as a monad-based computation
----------------------------------------------------------------------
formatGraph :: RDFGraph -> Formatter B.Builder
formatGraph gr = mconcat <$> mapM formatArc (S.toList (getArcs gr))
-- TODO: this reverses the contents but may be faster?
-- that is if I've got the order right in the mappend call
-- formatGraphBuilder gr = foldl' (\a b -> b `mappend` (formatArcBuilder a)) B.empty (getArcs gr)
space, nl :: B.Builder
space = B.singleton ' '
nl = " .\n"
formatArc :: Arc RDFLabel -> Formatter B.Builder
formatArc (Arc s p o) = do
sl <- formatLabel s
pl <- formatLabel p
ol <- formatLabel o
return $ mconcat [sl, space, pl, space, ol, nl]
formatLabel :: RDFLabel -> Formatter B.Builder
formatLabel lab@(Blank _) = mapBlankNode lab
formatLabel (Res sn) = return $ showScopedName sn
formatLabel (Lit lit) = return $ quoteText lit
formatLabel (LangLit lit lang) = return $ mconcat [quoteText lit, "@", B.fromText (fromLangTag lang)]
formatLabel (TypedLit lit dt) = return $ mconcat [quoteText lit, "^^", showScopedName dt]
-- do not expect to get the following, but include
-- just in case rather than failing
formatLabel lab = return $ B.fromString $ show lab
mapBlankNode :: RDFLabel -> Formatter B.Builder
mapBlankNode = mapBlankNode_ _nodeGen
-- TODO: can we use Network.URI to protect the URI?
showScopedName :: ScopedName -> B.Builder
showScopedName s = B.fromText (quote (T.pack (show (getQName s)))) -- looks like qname already adds the <> around this
quoteText :: T.Text -> B.Builder
quoteText st = mconcat ["\"", B.fromText (quote st), "\""]
{-
QUS: should we be operating on Text like this?
-}
quote :: T.Text -> T.Text
quote = T.concatMap quoteT
quoteT :: Char -> T.Text
quoteT '\\' = "\\\\"
quoteT '"' = "\\\""
quoteT '\n' = "\\n"
quoteT '\t' = "\\t"
quoteT '\r' = "\\r"
quoteT c =
let nc = ord c
in if nc > 0xffff
then T.pack ('\\':'U': numToHex 8 nc)
else if nc > 0x7e || nc < 0x20
then T.pack ('\\':'u': numToHex 4 nc)
else T.singleton c
-- we assume c > 0, n >= 0 and that the input value fits
-- into the requested number of digits
numToHex :: Int -> Int -> String
numToHex c = go []
where
go s 0 = replicate (c - length s) '0' ++ s
go s n =
let (m,x) = divMod n 16
in go (iToD x:s) m
-- Data.Char.intToDigit uses lower-case Hex
iToD x | x < 10 = intToDigit x
| otherwise = toUpper $ intToDigit x
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012, 2013, 2014, 2015, 2020 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/RDF/Formatter/N3.hs 0000644 0000000 0000000 00000040233 14163107250 016334 0 ustar 00 0000000 0000000 {-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
--------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : N3
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012, 2014, 2020, 2021 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : OverloadedStrings
--
-- This Module implements a Notation 3 formatter
-- for an 'RDFGraph' value.
--
-- REFERENCES:
--
-- - \"Notation3 (N3): A readable RDF syntax\",
-- W3C Team Submission 14 January 2008,
--
--
-- - Tim Berners-Lee's design issues series notes and description,
--
--
-- - Notation 3 Primer by Sean Palmer,
--
--
-- TODO:
--
-- * Initial prefix list to include nested formulae;
-- then don't need to update prefix list for these.
--
-- * correct output of strings containing unsupported escape
-- characters (such as @\\q@)
--
-- * more flexible terminator generation for formatted formulae
-- (for inline blank nodes.)
--
--------------------------------------------------------------------------------
{-
TODO:
The code used to determine whether a blank node can be written
using the "[]" short form could probably take advantage of the
GraphPartition module.
-}
module Swish.RDF.Formatter.N3
( NodeGenLookupMap
, formatGraphAsText
, formatGraphAsLazyText
, formatGraphAsBuilder
, formatGraphIndent
, formatGraphDiag
)
where
import Swish.RDF.Formatter.Internal (NodeGenLookupMap, SubjTree, PredTree
, SLens(..)
, LabelContext(..)
, NodeGenState(..)
, changeState
, hasMore
, emptyNgs
, findMaxBnode
, processArcs
, quoteB
, formatScopedName
, formatPlainLit
, formatLangLit
, formatTypedLit
, insertList
, nextLine_
, mapBlankNode_
, formatPrefixes_
, formatGraph_
, formatSubjects_
, formatProperties_
, formatObjects_
, insertBnode_
, extractList_
)
import Swish.Namespace (ScopedName)
import Swish.RDF.Graph (
RDFGraph, RDFLabel(..),
NamespaceMap,
emptyNamespaceMap,
FormulaMap, emptyFormulaMap,
setNamespaces, getNamespaces,
getFormulae,
emptyRDFGraph
)
import Swish.RDF.Vocabulary (
rdfType,
rdfNil,
owlSameAs, logImplies
)
import Control.Monad (void)
import Control.Monad.State (State, modify, get, gets, put, runState)
import Data.Char (isDigit)
import Data.List (uncons)
import Data.Word (Word32)
#if (!defined(__GLASGOW_HASKELL__)) || (__GLASGOW_HASKELL__ < 710)
import Data.Monoid (Monoid(..))
#endif
-- it strikes me that using Lazy Text here is likely to be
-- wrong; however I have done no profiling to back this
-- assumption up!
import qualified Data.Map as M
import qualified Data.Text as T
import qualified Data.Text.Lazy as L
import qualified Data.Text.Lazy.Builder as B
----------------------------------------------------------------------
-- Graph formatting state monad
----------------------------------------------------------------------
--
-- The graph to be formatted is carried as part of the formatting
-- state, so that decisions about what needs to be formatted can
-- themselves be based upon and reflected in the state (e.g. if a
-- decision is made to include a blank node inline, it can be removed
-- from the graph state that remains to be formatted).
data N3FormatterState = N3FS
{ indent :: B.Builder
, lineBreak :: Bool
, graph :: RDFGraph
, subjs :: SubjTree RDFLabel
, props :: PredTree RDFLabel -- for last subject selected
, objs :: [RDFLabel] -- for last property selected
, formAvail :: FormulaMap RDFLabel
, formQueue :: [(RDFLabel,RDFGraph)]
, prefixes :: NamespaceMap
, nodeGenSt :: NodeGenState
, bNodesCheck :: [RDFLabel] -- these bNodes are not to be converted to '[..]' format
, traceBuf :: [String]
}
type SL a = SLens N3FormatterState a
_lineBreak :: SL Bool
_lineBreak = SLens lineBreak $ \a b -> a { lineBreak = b }
_nodeGen :: SL NodeGenState
_nodeGen = SLens nodeGenSt $ \a b -> a { nodeGenSt = b }
type Formatter a = State N3FormatterState a
updateState :: N3FormatterState -> SubjTree RDFLabel -> PredTree RDFLabel -> [RDFLabel] -> N3FormatterState
updateState ost nsubjs nprops nobjs = ost { subjs = nsubjs, props = nprops, objs = nobjs }
emptyN3FS :: NamespaceMap -> NodeGenState -> N3FormatterState
emptyN3FS pmap ngs = N3FS
{ indent = "\n"
, lineBreak = False
, graph = emptyRDFGraph
, subjs = []
, props = []
, objs = []
, formAvail = emptyFormulaMap
, formQueue = []
, prefixes = pmap
, nodeGenSt = ngs
, bNodesCheck = []
, traceBuf = []
}
setIndent :: B.Builder -> Formatter ()
setIndent ind = modify $ \st -> st { indent = ind }
setLineBreak :: Bool -> Formatter ()
setLineBreak brk = modify $ \st -> st { lineBreak = brk }
setSubjs :: SubjTree RDFLabel -> Formatter ()
setSubjs sl = modify $ \st -> st { subjs = sl }
setProps :: PredTree RDFLabel -> Formatter ()
setProps ps = modify $ \st -> st { props = ps }
{-
getObjs :: Formatter ([RDFLabel])
getObjs = objs `fmap` get
setObjs :: [RDFLabel] -> Formatter ()
setObjs os = do
st <- get
put $ st { objs = os }
-}
{-
addTrace :: String -> Formatter ()
addTrace tr = do
st <- get
put $ st { traceBuf = tr : traceBuf st }
-}
queueFormula :: RDFLabel -> Formatter ()
queueFormula fn = do
st <- get
let fa = formAvail st
_newState fv = st {
formAvail = M.delete fn fa,
formQueue = (fn,fv) : formQueue st
}
case M.lookup fn fa of
Nothing -> return ()
Just v -> void $ put $ _newState v
{-
Return the graph associated with the label and delete it
from the store, if there is an association, otherwise
return Nothing.
-}
extractFormula :: RDFLabel -> Formatter (Maybe RDFGraph)
extractFormula fn = do
st <- get
let (rval, nform) = M.updateLookupWithKey (\_ _ -> Nothing) fn $ formAvail st
put $ st { formAvail = nform }
return rval
{-
moreFormulae :: Formatter Bool
moreFormulae = do
st <- get
return $ not $ null (formQueue st)
nextFormula :: Formatter (RDFLabel,RDFGraph)
nextFormula = do
st <- get
let (nf : fq) = formQueue st
put $ st { formQueue = fq }
return nf
-}
{-
TODO:
Should we change the preds/objs entries as well?
-}
extractList :: LabelContext -> RDFLabel -> Formatter (Maybe [RDFLabel])
extractList = extractList_ subjs props setSubjs setProps
----------------------------------------------------------------------
-- Define a top-level formatter function:
----------------------------------------------------------------------
-- | Convert the graph to text.
formatGraphAsText :: RDFGraph -> T.Text
formatGraphAsText = L.toStrict . formatGraphAsLazyText
-- | Convert the graph to text.
formatGraphAsLazyText :: RDFGraph -> L.Text
formatGraphAsLazyText = B.toLazyText . formatGraphAsBuilder
-- | Convert the graph to a Builder.
formatGraphAsBuilder :: RDFGraph -> B.Builder
formatGraphAsBuilder = formatGraphIndent "\n" True
-- | Convert the graph to a builder using the given indentation text.
formatGraphIndent ::
B.Builder -- ^ indentation text
-> Bool -- ^ are prefixes to be generated?
-> RDFGraph -- ^ graph
-> B.Builder
formatGraphIndent indnt flag gr =
let (res, _, _, _) = formatGraphDiag indnt flag gr
in res
-- | Format graph and return additional information
formatGraphDiag ::
B.Builder -- ^ indentation
-> Bool -- ^ are prefixes to be generated?
-> RDFGraph
-> (B.Builder, NodeGenLookupMap, Word32, [String])
formatGraphDiag indnt flag gr =
let fg = formatGraph indnt " .\n" False flag gr
ngs = emptyNgs { nodeGen = findMaxBnode gr }
(out, fgs) = runState fg (emptyN3FS emptyNamespaceMap ngs)
ogs = nodeGenSt fgs
in (out, nodeMap ogs, nodeGen ogs, traceBuf fgs)
----------------------------------------------------------------------
-- Formatting as a monad-based computation
----------------------------------------------------------------------
formatGraph ::
B.Builder -- indentation string
-> B.Builder -- text to be placed after final statement
-> Bool -- True if a line break is to be inserted at the start
-> Bool -- True if prefix strings are to be generated
-> RDFGraph -- graph to convert
-> Formatter B.Builder
formatGraph = formatGraph_ setIndent setLineBreak newState formatPrefixes subjs formatSubjects
formatPrefixes :: NamespaceMap -> Formatter B.Builder
formatPrefixes = formatPrefixes_ nextLine
formatSubjects :: Formatter B.Builder
formatSubjects = formatSubjects_ nextSubject formatLabel props formatProperties subjs nextLine
formatProperties :: RDFLabel -> B.Builder -> Formatter B.Builder
formatProperties = formatProperties_ nextProperty formatLabel formatObjects props nextLine
formatObjects :: RDFLabel -> RDFLabel -> B.Builder -> Formatter B.Builder
formatObjects = formatObjects_ nextObject formatLabel objs nextLine
insertFormula :: RDFGraph -> Formatter B.Builder
insertFormula gr = do
pmap0 <- gets prefixes
ngs0 <- gets nodeGenSt
ind <- gets indent
let grm = formatGraph (ind `mappend` " ") "" True False
(setNamespaces emptyNamespaceMap gr)
(f3str, fgs') = runState grm (emptyN3FS pmap0 ngs0)
modify $ \st -> st { nodeGenSt = nodeGenSt fgs'
, prefixes = prefixes fgs' }
f4str <- nextLine " } "
return $ mconcat [" { ",f3str, f4str]
{-
Add a blank node inline.
-}
insertBnode :: LabelContext -> RDFLabel -> Formatter B.Builder
insertBnode SubjContext lbl = do
flag <- hasMore props
txt <- if flag
then (`mappend` "\n") `fmap` formatProperties lbl ""
else return ""
-- TODO: handle indentation?
return $ mconcat ["[", txt, "]"]
insertBnode _ lbl = insertBnode_ subjs props objs updateState formatProperties lbl
----------------------------------------------------------------------
-- Formatting helpers
----------------------------------------------------------------------
newState :: RDFGraph -> N3FormatterState -> N3FormatterState
newState gr st =
let pre' = prefixes st `M.union` getNamespaces gr
(arcSubjs, bNodes) = processArcs gr
in st { graph = gr
, subjs = arcSubjs
, props = []
, objs = []
, formAvail = getFormulae gr
, prefixes = pre'
, bNodesCheck = bNodes
}
-- A version of uncons for a list which is not empty but we haven't
-- encoded that invariant.
--
getNext :: [a] -> (a, [a])
getNext xs = case uncons xs of
Just (a, as) -> (a, as)
Nothing -> error "Invariant broken: list is empty"
nextSubject :: Formatter RDFLabel
nextSubject =
changeState $ \st ->
let ((a,b), sbs) = getNext (subjs st)
nst = st { subjs = sbs
, props = b
, objs = []
}
in (a, nst)
nextProperty :: RDFLabel -> Formatter RDFLabel
nextProperty _ =
changeState $ \st ->
let ((a,b), prs) = getNext (props st)
nst = st { props = prs
, objs = b
}
in (a, nst)
nextObject :: RDFLabel -> RDFLabel -> Formatter RDFLabel
nextObject _ _ =
changeState $ \st ->
let (ob, obs) = getNext (objs st)
nst = st { objs = obs }
in (ob, nst)
nextLine :: B.Builder -> Formatter B.Builder
nextLine = nextLine_ indent _lineBreak
-- Format a label
-- Most labels are simply displayed as provided, but there are a
-- number of wrinkles to take care of here:
-- (a) blank nodes automatically allocated on input, with node
-- identifiers of the form of a digit string nnn. These are
-- not syntactically valid, and are reassigned node identifiers
-- of the form _nnn, where nnn is chosen so that is does not
-- clash with any other identifier in the graph.
-- (b) URI nodes: if possible, replace URI with qname,
-- else display as
-- (c) formula nodes (containing graphs).
-- (d) use the "special-case" formats for integer/float/double
-- literals.
--
-- [[[TODO:]]]
-- (d) generate multi-line literals when appropriate
--
-- This is being updated to produce inline formula, lists and
-- blank nodes. The code is not efficient.
--
specialTable :: [(ScopedName, String)]
specialTable =
[ (rdfType, "a")
, (owlSameAs, "=")
, (logImplies, "=>")
, (rdfNil, "()")
]
formatLabel :: LabelContext -> RDFLabel -> Formatter B.Builder
{-
formatLabel lab@(Blank (_:_)) = do
name <- formatNodeId lab
queueFormula lab
return name
-}
{-
The "[..]" conversion is done last, after "()" and "{}" checks.
TODO: look at the (_:_) check on the blank string; why is this needed?
-}
formatLabel lctxt lab@(Blank (_:_)) = do
mlst <- extractList lctxt lab
case mlst of
Just lst -> insertList (formatLabel ObjContext) lst
Nothing -> do
mfml <- extractFormula lab
case mfml of
Just fml -> insertFormula fml
Nothing -> do
nb1 <- gets bNodesCheck
if lctxt /= PredContext && lab `notElem` nb1
then insertBnode lctxt lab
else formatNodeId lab
formatLabel _ lab@(Res sn) =
case lookup sn specialTable of
Just txt -> return $ quoteB True txt -- TODO: do we need to quote?
Nothing -> do
pr <- gets prefixes
queueFormula lab
return $ formatScopedName sn pr
formatLabel _ (Lit lit) = return $ formatPlainLit lit
formatLabel _ (LangLit lit lcode) = return $ formatLangLit lit lcode
formatLabel _ (TypedLit lit dtype) = return $ formatTypedLit True lit dtype
formatLabel _ lab = return $ B.fromString $ show lab
formatNodeId :: RDFLabel -> Formatter B.Builder
formatNodeId lab@(Blank (lnc:_)) =
if isDigit lnc then mapBlankNode lab else return $ B.fromString $ show lab
formatNodeId other = error $ "formatNodeId not expecting a " ++ show other -- to shut up -Wall
mapBlankNode :: RDFLabel -> Formatter B.Builder
mapBlankNode = mapBlankNode_ _nodeGen
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012, 2014, 2020, 2021 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/RDF/Formatter/Turtle.hs 0000644 0000000 0000000 00000034001 14163107250 017327 0 ustar 00 0000000 0000000 {-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
--------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : Turtle
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012, 2013, 2014, 2018, 2019, 2020, 2021 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : CPP, OverloadedStrings
--
-- This Module implements a Turtle formatter
-- for an 'RDFGraph' value.
--
-- REFERENCES:
--
-- - \"Turtle, Terse RDF Triple Language\",
-- W3C Working Draft 09 August 2011 ()
--
--
-- NOTES:
--
-- - The formatter needs to be updated to the W3C
-- Candidate Recommendation (19 February 2013,
-- ).
--
-- - Should literal strings (@Lit@) be written out as @xsd:string@, or
-- should @TypedLit@ strings with a type of @xsd:string@ be written
-- out with no type? (e.g. see
-- ).
--
--------------------------------------------------------------------------------
{-
TODO:
The code used to determine whether a blank node can be written
using the "[]" short form could probably take advantage of the
GraphPartition module.
-}
module Swish.RDF.Formatter.Turtle
( NodeGenLookupMap
, formatGraphAsText
, formatGraphAsLazyText
, formatGraphAsBuilder
, formatGraphIndent
, formatGraphDiag
)
where
import Swish.RDF.Formatter.Internal (NodeGenLookupMap, SubjTree, PredTree
, SLens(..)
, LabelContext(..)
, NodeGenState(..)
, changeState
, hasMore
, emptyNgs
, findMaxBnode
, processArcs
, formatScopedName
, formatPlainLit
, formatLangLit
, formatTypedLit
, insertList
, nextLine_
, mapBlankNode_
, formatPrefixes_
, formatGraph_
, formatSubjects_
, formatProperties_
, formatObjects_
, insertBnode_
, extractList_
)
import Swish.RDF.Graph (
RDFGraph, RDFLabel(..)
, NamespaceMap
, emptyNamespaceMap
, getNamespaces
, emptyRDFGraph
)
import Swish.RDF.Vocabulary (rdfType, rdfNil)
#if (!defined(__GLASGOW_HASKELL__)) || (__GLASGOW_HASKELL__ < 808)
import Control.Applicative ((<$>))
#endif
import Control.Monad.State (State, modify, gets, runState)
import Data.Char (isDigit)
import Data.List (uncons)
import Data.Word (Word32)
#if (!defined(__GLASGOW_HASKELL__)) || (__GLASGOW_HASKELL__ < 710)
import Data.Monoid (Monoid(..))
#endif
-- it strikes me that using Lazy Text here is likely to be
-- wrong; however I have done no profiling to back this
-- assumption up!
import qualified Data.Map as M
import qualified Data.Text as T
import qualified Data.Text.Lazy as L
import qualified Data.Text.Lazy.Builder as B
----------------------------------------------------------------------
-- Graph formatting state monad
----------------------------------------------------------------------
--
-- The graph to be formatted is carried as part of the formatting
-- state, so that decisions about what needs to be formatted can
-- themselves be based upon and reflected in the state (e.g. if a
-- decision is made to include a blank node inline, it can be removed
-- from the graph state that remains to be formatted).
data TurtleFormatterState = TFS
{ indent :: B.Builder
, lineBreak :: Bool
, graph :: RDFGraph
, subjs :: SubjTree RDFLabel
, props :: PredTree RDFLabel -- for last subject selected
, objs :: [RDFLabel] -- for last property selected
-- , formAvail :: FormulaMap RDFLabel
-- , formQueue :: [(RDFLabel,RDFGraph)]
, prefixes :: NamespaceMap
, nodeGenSt :: NodeGenState
, bNodesCheck :: [RDFLabel] -- these bNodes are not to be converted to '[..]' format
, traceBuf :: [String]
}
type SL a = SLens TurtleFormatterState a
_lineBreak :: SL Bool
_lineBreak = SLens lineBreak $ \a b -> a { lineBreak = b }
_nodeGen :: SL NodeGenState
_nodeGen = SLens nodeGenSt $ \a b -> a { nodeGenSt = b }
type Formatter a = State TurtleFormatterState a
updateState ::
TurtleFormatterState
-> SubjTree RDFLabel
-> PredTree RDFLabel
-> [RDFLabel]
-> TurtleFormatterState
updateState ost nsubjs nprops nobjs = ost { subjs = nsubjs, props = nprops, objs = nobjs }
emptyTFS :: NodeGenState -> TurtleFormatterState
emptyTFS ngs = TFS
{ indent = "\n"
, lineBreak = False
, graph = emptyRDFGraph
, subjs = []
, props = []
, objs = []
, prefixes = emptyNamespaceMap
, nodeGenSt = ngs
, bNodesCheck = []
, traceBuf = []
}
setIndent :: B.Builder -> Formatter ()
setIndent ind = modify $ \st -> st { indent = ind }
setLineBreak :: Bool -> Formatter ()
setLineBreak brk = modify $ \st -> st { lineBreak = brk }
setSubjs :: SubjTree RDFLabel -> Formatter ()
setSubjs sl = modify $ \st -> st { subjs = sl }
setProps :: PredTree RDFLabel -> Formatter ()
setProps ps = modify $ \st -> st { props = ps }
{-
TODO:
Should we change the preds/objs entries as well?
-}
extractList :: LabelContext -> RDFLabel -> Formatter (Maybe [RDFLabel])
extractList = extractList_ subjs props setSubjs setProps
----------------------------------------------------------------------
-- Define a top-level formatter function:
----------------------------------------------------------------------
-- | Convert the graph to text.
formatGraphAsText :: RDFGraph -> T.Text
formatGraphAsText = L.toStrict . formatGraphAsLazyText
-- | Convert the graph to text.
formatGraphAsLazyText :: RDFGraph -> L.Text
formatGraphAsLazyText = B.toLazyText . formatGraphAsBuilder
-- | Convert the graph to a Builder.
formatGraphAsBuilder :: RDFGraph -> B.Builder
formatGraphAsBuilder = formatGraphIndent "\n" True
-- | Convert the graph to a builder using the given indentation text.
formatGraphIndent ::
B.Builder -- ^ indentation text
-> Bool -- ^ are prefixes to be generated?
-> RDFGraph -- ^ graph
-> B.Builder
formatGraphIndent indnt flag gr =
let (res, _, _, _) = formatGraphDiag indnt flag gr
in res
-- | Format graph and return additional information.
formatGraphDiag ::
B.Builder -- ^ indentation
-> Bool -- ^ are prefixes to be generated?
-> RDFGraph
-> (B.Builder, NodeGenLookupMap, Word32, [String])
formatGraphDiag indnt flag gr =
let fg = formatGraph indnt " .\n" False flag gr
ngs = emptyNgs { nodeGen = findMaxBnode gr }
(out, fgs) = runState fg (emptyTFS ngs)
ogs = nodeGenSt fgs
in (out, nodeMap ogs, nodeGen ogs, traceBuf fgs)
----------------------------------------------------------------------
-- Formatting as a monad-based computation
----------------------------------------------------------------------
formatGraph ::
B.Builder -- indentation string
-> B.Builder -- text to be placed after final statement
-> Bool -- True if a line break is to be inserted at the start
-> Bool -- True if prefix strings are to be generated
-> RDFGraph -- graph to convert
-> Formatter B.Builder
formatGraph = formatGraph_ setIndent setLineBreak newState formatPrefixes subjs formatSubjects
formatPrefixes :: NamespaceMap -> Formatter B.Builder
formatPrefixes = formatPrefixes_ nextLine
formatSubjects :: Formatter B.Builder
formatSubjects = formatSubjects_ nextSubject formatLabel props formatProperties subjs nextLine
formatProperties :: RDFLabel -> B.Builder -> Formatter B.Builder
formatProperties = formatProperties_ nextProperty formatLabel formatObjects props nextLine
formatObjects :: RDFLabel -> RDFLabel -> B.Builder -> Formatter B.Builder
formatObjects = formatObjects_ nextObject formatLabel objs nextLine
{-
Add a blank node inline.
-}
insertBnode :: LabelContext -> RDFLabel -> Formatter B.Builder
insertBnode SubjContext lbl = do
-- a safety check
flag <- hasMore props
if flag
then do
txt <- (`mappend` "\n") `fmap` formatProperties lbl ""
return $ mconcat ["[] ", txt]
else error $ "Internal error: expected properties with label: " ++ show lbl
insertBnode _ lbl = insertBnode_ subjs props objs updateState formatProperties lbl
----------------------------------------------------------------------
-- Formatting helpers
----------------------------------------------------------------------
newState :: RDFGraph -> TurtleFormatterState -> TurtleFormatterState
newState gr st =
let pre' = prefixes st `M.union` getNamespaces gr
(arcSubjs, bNodes) = processArcs gr
in st { graph = gr
, subjs = arcSubjs
, props = []
, objs = []
, prefixes = pre'
, bNodesCheck = bNodes
}
-- A version of uncons for a list which is not empty but we haven't
-- encoded that invariant.
--
getNext :: [a] -> (a, [a])
getNext xs = case uncons xs of
Just (a, as) -> (a, as)
Nothing -> error "Invariant broken: list is empty"
nextSubject :: Formatter RDFLabel
nextSubject =
changeState $ \st ->
let ((a,b), sbs) = getNext (subjs st)
nst = st { subjs = sbs
, props = b
, objs = []
}
in (a, nst)
nextProperty :: RDFLabel -> Formatter RDFLabel
nextProperty _ =
changeState $ \st ->
let ((a,b), prs) = getNext (props st)
nst = st { props = prs
, objs = b
}
in (a, nst)
nextObject :: RDFLabel -> RDFLabel -> Formatter RDFLabel
nextObject _ _ =
changeState $ \st ->
let (ob, obs) = getNext (objs st)
nst = st { objs = obs }
in (ob, nst)
nextLine :: B.Builder -> Formatter B.Builder
nextLine = nextLine_ indent _lineBreak
-- Format a label
-- Most labels are simply displayed as provided, but there are a
-- number of wrinkles to take care of here:
-- (a) blank nodes automatically allocated on input, with node
-- identifiers of the form of a digit string nnn. These are
-- not syntactically valid, and are reassigned node identifiers
-- of the form _nnn, where nnn is chosen so that is does not
-- clash with any other identifier in the graph.
-- (b) URI nodes: if possible, replace URI with qname,
-- else display as
-- (c) formula nodes (containing graphs).
-- (d) use the "special-case" formats for integer/float/double/string
-- literals.
--
-- This is being updated to produce inline formula, lists and
-- blank nodes. The code is not efficient.
--
-- Note: There is a lot less customisation possible in Turtle than N3.
--
formatLabel :: LabelContext -> RDFLabel -> Formatter B.Builder
{-
The "[..]" conversion is done last, after "()" and "{}" checks.
TODO: why is there a (_:_) check on the blank node?
-}
formatLabel lctxt lab@(Blank (_:_)) = do
mlst <- extractList lctxt lab
case mlst of
Just lst -> insertList (formatLabel ObjContext) lst
Nothing -> do
-- NOTE: unlike N3 we do not properly handle "formula"/named graphs
-- also we only expand out bnodes into [...] format when it's a object.
-- although we need to handle [] for the subject.
nb1 <- gets bNodesCheck
if lctxt /= PredContext && lab `notElem` nb1
then insertBnode lctxt lab
else formatNodeId lab
-- formatLabel _ lab@(Res sn) =
formatLabel ctxt (Res sn)
| ctxt == PredContext && sn == rdfType = return "a"
| ctxt == ObjContext && sn == rdfNil = return "()"
| otherwise = gets (formatScopedName sn . prefixes)
formatLabel _ (Lit lit) = return $ formatPlainLit lit
formatLabel _ (LangLit lit lcode) = return $ formatLangLit lit lcode
formatLabel _ (TypedLit lit dtype) = return $ formatTypedLit False lit dtype
formatLabel _ lab = return $ B.fromString $ show lab
formatNodeId :: RDFLabel -> Formatter B.Builder
formatNodeId lab@(Blank (lnc:_)) =
if isDigit lnc then mapBlankNode lab else return $ B.fromString $ show lab
formatNodeId other = error $ "formatNodeId not expecting a " ++ show other -- to shut up -Wall
mapBlankNode :: RDFLabel -> Formatter B.Builder
mapBlankNode = mapBlankNode_ _nodeGen
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012, 2013, 2014, 2018, 2019, 2020, 2021 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/RDF/Graph.hs 0000644 0000000 0000000 00000155434 14220136201 015154 0 ustar 00 0000000 0000000 {-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
--------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : Graph
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012, 2013, 2014, 2015, 2016, 2018, 2020. 2022 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : CPP, FlexibleInstances, MultiParamTypeClasses, OverloadedStrings
--
-- This module defines a memory-based RDF graph instance. At present only
-- RDF 1.0 is explicitly supported; I have not gone through the RDF 1.1
-- changes to see how the code needs to be updated. This means that you
-- can have untyped strings in your graph that do not match the same content
-- but with an explicit @xsd:string@ datatype.
--
-- Note that the identifiers for blank nodes may /not/ be propogated when
-- a graph is written out using one of the formatters, such as
-- 'Swish.RDF.Formatter.Turtle'. There is limited support for
-- generating new blank nodes from an existing set of triples; e.g.
-- 'newNode' and 'newNodes'.
--
--------------------------------------------------------------------------------
------------------------------------------------------------
-- Simple labelled directed graph value
------------------------------------------------------------
module Swish.RDF.Graph
(
-- * Labels
RDFLabel(..), ToRDFLabel(..), FromRDFLabel(..)
, isLiteral, isUntypedLiteral, isTypedLiteral, isXMLLiteral
, isDatatyped, isMemberProp, isUri, isBlank, isQueryVar
, getLiteralText, getScopedName, makeBlank
, quote
, quoteT
-- * RDF Graphs
, RDFArcSet
, RDFTriple
, toRDFTriple, fromRDFTriple
, NSGraph(..)
, RDFGraph
, toRDFGraph, emptyRDFGraph {-, updateRDFGraph-}
, NamespaceMap
, emptyNamespaceMap
, LookupFormula(..), Formula, FormulaMap, emptyFormulaMap
, addArc, merge
, allLabels, allNodes, remapLabels, remapLabelList
, newNode, newNodes
, setNamespaces, getNamespaces
, setFormulae, getFormulae, setFormula, getFormula
, fmapNSGraph
, traverseNSGraph
-- * Re-export from GraphClass
--
-- | Note that @asubj@, @apred@ and @aobj@ have been
-- removed in version @0.7.0.0@; use 'arcSubj', 'arcPred'
-- or 'arcObj' instead.
--
, LDGraph(..), Label (..), Arc(..)
, arc, Selector
-- * Selected RDFLabel values
--
-- | The 'ToRDFLabel' instance of 'ScopedName' can also be used
-- to easily construct 'RDFLabel' versions of the terms defined
-- in "Swish.RDF.Vocabulary".
-- ** RDF terms
--
-- | These terms are described in ;
-- the version used is \"W3C Recommendation 10 February 2004\", .
--
-- Some terms are listed within the RDF Schema terms below since their definition
-- is given within the RDF Schema document.
--
, resRdfRDF
, resRdfDescription
, resRdfID
, resRdfAbout
, resRdfParseType
, resRdfResource
, resRdfLi
, resRdfNodeID
, resRdfDatatype
, resRdf1, resRdf2, resRdfn
-- ** RDF Schema terms
--
-- | These are defined by ; the version
-- used is \"W3C Recommendation 10 February 2004\", .
-- *** Classes
--
-- | See the \"Classes\" section at for more information.
, resRdfsResource
, resRdfsClass
, resRdfsLiteral
, resRdfsDatatype
, resRdfXMLLiteral
, resRdfProperty
-- *** Properties
--
-- | See the \"Properties\" section at for more information.
, resRdfsRange
, resRdfsDomain
, resRdfType
, resRdfsSubClassOf
, resRdfsSubPropertyOf
, resRdfsLabel
, resRdfsComment
-- *** Containers
--
-- | See the \"Container Classes and Properties\" section at .
, resRdfsContainer
, resRdfBag
, resRdfSeq
, resRdfAlt
, resRdfsContainerMembershipProperty
, resRdfsMember
-- *** Collections
--
-- | See the \"Collections\" section at .
, resRdfList
, resRdfFirst
, resRdfRest
, resRdfNil
-- *** Reification Vocabulary
--
-- | See the \"Reification Vocabulary\" section at .
, resRdfStatement
, resRdfSubject
, resRdfPredicate
, resRdfObject
-- *** Utility Properties
--
-- | See the \"Utility Properties\" section at .
, resRdfsSeeAlso
, resRdfsIsDefinedBy
, resRdfValue
-- ** OWL
, resOwlSameAs
-- ** Miscellaneous
, resRdfdGeneralRestriction
, resRdfdOnProperties, resRdfdConstraint, resRdfdMaxCardinality
, resLogImplies
-- * Exported for testing
, grMatchMap, grEq
, mapnode, maplist
)
where
import Swish.Namespace
( getNamespaceTuple
, getScopedNameURI
, ScopedName
, getScopeLocal, getScopeNamespace
, getQName
, makeQNameScopedName
, makeURIScopedName
, nullScopedName
)
import Swish.RDF.Vocabulary (LanguageTag)
import Swish.RDF.Vocabulary (fromLangTag, xsdBoolean, xsdDate, xsdDateTime, xsdDecimal, xsdDouble, xsdFloat, xsdInteger
, rdfType, rdfList, rdfFirst, rdfRest, rdfNil
, rdfsMember, rdfdGeneralRestriction, rdfdOnProperties, rdfdConstraint, rdfdMaxCardinality
, rdfsSeeAlso, rdfValue, rdfsLabel, rdfsComment, rdfProperty
, rdfsSubPropertyOf, rdfsSubClassOf, rdfsClass, rdfsLiteral
, rdfsDatatype, rdfXMLLiteral, rdfsRange, rdfsDomain, rdfsContainer
, rdfBag, rdfSeq, rdfAlt
, rdfsContainerMembershipProperty, rdfsIsDefinedBy
, rdfsResource, rdfStatement, rdfSubject, rdfPredicate, rdfObject
, rdfRDF, rdfDescription, rdfID, rdfAbout, rdfParseType
, rdfResource, rdfLi, rdfNodeID, rdfDatatype, rdfXMLLiteral
, rdf1, rdf2, rdfn
, owlSameAs, logImplies, namespaceRDF
)
import Swish.GraphClass (LDGraph(..), Label (..), Arc(..), ArcSet, Selector)
import Swish.GraphClass (arc, arcLabels, getComponents)
import Swish.GraphMatch (LabelMap, ScopedLabel(..))
import Swish.GraphMatch (graphMatch)
import Swish.QName (QName, getLName)
#if (!defined(__GLASGOW_HASKELL__)) || (__GLASGOW_HASKELL__ < 710)
import Control.Applicative (Applicative(pure), (<$>), (<*>))
import Data.Monoid (Monoid(..))
#endif
import Control.Arrow ((***))
import Network.URI (URI)
import Data.Maybe (mapMaybe)
import Data.Char (ord, isDigit)
import Data.Hashable (hashWithSalt)
import Data.List (intersect, union, foldl')
-- import Data.Ord (comparing)
import Data.Word (Word32)
import Data.String (IsString(..))
#if MIN_VERSION_time(1,5,0)
import Data.Time (UTCTime, Day, ParseTime, parseTimeM, formatTime, defaultTimeLocale)
#else
import Data.Time (UTCTime, Day, ParseTime, parseTime, formatTime)
import System.Locale (defaultTimeLocale)
#endif
#if !(MIN_VERSION_base(4, 11, 0))
import Data.Semigroup
#endif
import Text.Printf
import qualified Data.Map as M
import qualified Data.Set as S
import qualified Data.Text as T
import qualified Data.Text.Read as T
import qualified Data.Traversable as Traversable
-- | RDF graph node values
--
-- cf. version 1.0
--
-- This is extended from the RDF abstract graph syntax in the
-- following ways:
--
-- (a) a graph can be part of a resource node or blank node
-- (cf. Notation3 formulae)
--
-- (b) a \"variable\" node option is distinguished from a
-- blank node.
-- I have found this useful for encoding and handling
-- queries, even though query variables can be expressed
-- as blank nodes.
--
-- (c) a \"NoNode\" option is defined.
-- This might otherwise be handled by @Maybe (RDFLabel g)@.
--
-- Prior to version @0.7.0.0@, literals were represented by a
-- single constructor, @Lit@, with an optional argument. Language
-- codes for literals was also stored as a 'ScopedName' rather than
-- as a 'LanguageTag'.
--
data RDFLabel =
Res ScopedName -- ^ resource
| Lit T.Text -- ^ plain literal ()
| LangLit T.Text LanguageTag -- ^ plain literal
| TypedLit T.Text ScopedName -- ^ typed literal ()
| Blank String -- ^ blank node
| Var String -- ^ variable (not used in ordinary graphs)
| NoNode -- ^ no node (not used in ordinary graphs)
-- | Define equality of nodes possibly based on different graph types.
--
-- The equality of literals is taken from section 6.5.1 ("Literal
-- Equality") of the RDF Concepts and Abstract Document,
-- .
--
instance Eq RDFLabel where
Res q1 == Res q2 = q1 == q2
Blank b1 == Blank b2 = b1 == b2
Var v1 == Var v2 = v1 == v2
Lit s1 == Lit s2 = s1 == s2
LangLit s1 l1 == LangLit s2 l2 = s1 == s2 && l1 == l2
TypedLit s1 t1 == TypedLit s2 t2 = s1 == s2 && t1 == t2
_ == _ = False
instance Show RDFLabel where
show (Res sn) = show sn
show (Lit st) = quote1Str st
show (LangLit st lang) = quote1Str st ++ "@" ++ T.unpack (fromLangTag lang)
show (TypedLit st dtype)
| dtype `elem` [xsdBoolean, xsdDouble, xsdDecimal, xsdInteger] = T.unpack st
| otherwise = quote1Str st ++ "^^" ++ show dtype
{-
show (Lit st (Just nam))
| isLang nam = quote1Str st ++ "@" ++ T.unpack (langTag nam)
| nam `elem` [xsdBoolean, xsdDouble, xsdDecimal, xsdInteger] = T.unpack st
| otherwise = quote1Str st ++ "^^" ++ show nam
-}
show (Blank ln) = "_:" ++ ln
show (Var ln) = '?' : ln
show NoNode = ""
instance Ord RDFLabel where
-- Order, from lowest to highest is
-- Res, Lit, LangLit, TypedLit, Blank, Var, NoNode
--
compare (Res sn1) (Res sn2) = compare sn1 sn2
compare (Res _) _ = LT
compare _ (Res _) = GT
compare (Lit s1) (Lit s2) = compare s1 s2
compare (Lit _) _ = LT
compare _ (Lit _) = GT
compare (LangLit s1 l1) (LangLit s2 l2) = compare (s1,l1) (s2,l2)
compare (LangLit _ _) _ = LT
compare _ (LangLit _ _) = GT
compare (TypedLit s1 t1) (TypedLit s2 t2) = compare (s1,t1) (s2,t2)
compare (TypedLit _ _) _ = LT
compare _ (TypedLit _ _) = GT
compare (Blank ln1) (Blank ln2) = compare ln1 ln2
compare (Blank _) _ = LT
compare _ (Blank _) = GT
compare (Var ln1) (Var ln2) = compare ln1 ln2
compare (Var _) NoNode = LT
compare _ (Var _) = GT
compare NoNode NoNode = EQ
instance Label RDFLabel where
labelIsVar (Blank _) = True
labelIsVar (Var _) = True
labelIsVar _ = False
getLocal (Blank loc) = loc
getLocal (Var loc) = '?':loc
getLocal (Res sn) = "Res_" ++ (T.unpack . getLName . getScopeLocal) sn
getLocal NoNode = "None"
getLocal _ = "Lit_"
makeLabel ('?':loc) = Var loc
makeLabel loc = Blank loc
labelHash seed lb = hashWithSalt seed (showCanon lb)
instance IsString RDFLabel where
fromString = Lit . T.pack
{-|
A type that can be converted to a RDF Label.
The String instance converts to an untyped literal
(so no language tag is assumed).
The `UTCTime` and `Day` instances assume values are in UTC.
The conversion for XSD types attempts to use the
canonical form described in section 2.3.1 of
.
Note that this is similar to
'Swish.RDF.Datatype.toRDFLabel';
the code should probably be combined at some point.
-}
class ToRDFLabel a where
toRDFLabel :: a -> RDFLabel
{-|
A type that can be converted from a RDF Label,
with the possibility of failure.
The String instance converts from an untyped literal
(so it can not be used with a string with a language tag).
The following conversions are supported for common XSD
types (out-of-band values result in @Nothing@):
- @xsd:boolean@ to @Bool@
- @xsd:integer@ to @Int@ and @Integer@
- @xsd:float@ to @Float@
- @xsd:double@ to @Double@
- @xsd:dateTime@ to @UTCTime@
- @xsd:date@ to @Day@
Note that this is similar to
'Swish.RDF.Datatype.fromRDFLabel';
the code should probably be combined at some point.
-}
class FromRDFLabel a where
fromRDFLabel :: RDFLabel -> Maybe a
-- instances for type conversion to/from RDFLabel
-- | This is just @id@.
instance ToRDFLabel RDFLabel where
toRDFLabel = id
-- | This is just @Just@.
instance FromRDFLabel RDFLabel where
fromRDFLabel = Just
-- TODO: remove this hack when finished conversion to Text
maybeReadStr :: (Read a) => T.Text -> Maybe a
maybeReadStr txt = case reads (T.unpack txt) of
[(val, "")] -> Just val
_ -> Nothing
maybeRead :: T.Reader a -> T.Text -> Maybe a
maybeRead rdr inTxt =
case rdr inTxt of
Right (val, "") -> Just val
_ -> Nothing
fLabel :: (T.Text -> Maybe a) -> ScopedName -> RDFLabel -> Maybe a
fLabel conv dtype (TypedLit xs dt) | dt == dtype = conv xs
| otherwise = Nothing
fLabel _ _ _ = Nothing
tLabel :: (Show a) => ScopedName -> (String -> T.Text) -> a -> RDFLabel
tLabel dtype conv = flip TypedLit dtype . conv . show
-- | The character is converted to an untyped literal of length one.
instance ToRDFLabel Char where
toRDFLabel = Lit . T.singleton
-- | The label must be an untyped literal containing a single character.
instance FromRDFLabel Char where
fromRDFLabel (Lit cs) | T.compareLength cs 1 == EQ = Just (T.head cs)
| otherwise = Nothing
fromRDFLabel _ = Nothing
-- | Strings are converted to untyped literals.
instance ToRDFLabel String where
toRDFLabel = Lit . T.pack
-- | Only untyped literals are converted to strings.
instance FromRDFLabel String where
fromRDFLabel (Lit xs) = Just (T.unpack xs)
fromRDFLabel _ = Nothing
textToBool :: T.Text -> Maybe Bool
textToBool s | s `elem` ["1", "true"] = Just True
| s `elem` ["0", "false"] = Just False
| otherwise = Nothing
-- | Converts to a literal with a @xsd:boolean@ datatype.
instance ToRDFLabel Bool where
toRDFLabel b = TypedLit (if b then "true" else "false") xsdBoolean
-- | Converts from a literal with a @xsd:boolean@ datatype. The
-- literal can be any of the supported XSD forms - e.g. \"0\" or
-- \"true\".
instance FromRDFLabel Bool where
fromRDFLabel = fLabel textToBool xsdBoolean
-- fromRealFloat :: (RealFloat a, Buildable a) => ScopedName -> a -> RDFLabel
fromRealFloat :: (RealFloat a, PrintfArg a) => ScopedName -> a -> RDFLabel
fromRealFloat dtype f | isNaN f = toL "NaN"
| isInfinite f = toL $ if f > 0.0 then "INF" else "-INF"
--
-- Would like to use Data.Text.Format.format but there are
-- issues with this module; 0.3.0.2 doesn't build under
-- 6.12.3 due to a missing RelaxedPolyRec language extension
-- and it relies on double-conversion which has issues
-- when used in ghci due to a dlopen issue with libstdc++.
--
-- -- | otherwise = toL $ L.toStrict $ format "{}" (Only f)
--
| otherwise = toL $ T.pack $ printf "%E" f
where
toL = flip TypedLit dtype
-- textToRealFloat :: (RealFloat a) => (a -> Maybe a) -> T.Text -> Maybe a
textToRealFloat :: (RealFloat a, Read a) => (a -> Maybe a) -> T.Text -> Maybe a
textToRealFloat conv = rconv
where
rconv "NaN" = Just (0.0 / 0.0) -- how best to create a NaN?
rconv "INF" = Just (1.0 / 0.0) -- ditto for Infinity
rconv "-INF" = Just ((-1.0) / 0.0)
rconv ival
-- xsd semantics allows "2." but Haskell syntax does not.
| T.null ival = Nothing
| otherwise = case maybeReadStr ival of
Just val -> conv val
_ -> if T.last ival == '.' -- could drop the check
then maybeReadStr (T.snoc ival '0') >>= conv
else Nothing
{-
Unfortunately T.rational does not handle "3.01e4" the same
as read; see https://bitbucket.org/bos/text/issue/7/
| otherwise = case maybeRead T.rational ival of
Just val -> conv val
_ -> if T.last ival == '.' -- could drop the check
then maybeRead T.rational (T.snoc ival '0') >>= conv
else Nothing
-}
-- not sure the above is any improvement on the following
-- -- | T.last ival == '.' = maybeRead T.rational (T.snoc ival '0') >>= conv
-- -- | otherwise = maybeRead T.rational ival >>= conv
textToFloat :: T.Text -> Maybe Float
textToFloat =
let -- assume that an invalid value (NaN/Inf) from maybeRead means
-- that the value is out of bounds for Float so we do not
-- convert
conv f | isNaN f || isInfinite f = Nothing
| otherwise = Just f
in textToRealFloat conv
textToDouble :: T.Text -> Maybe Double
textToDouble = textToRealFloat Just
-- | Converts to a literal with a @xsd:float@ datatype.
instance ToRDFLabel Float where
toRDFLabel = fromRealFloat xsdFloat
-- | Converts from a literal with a @xsd:float@ datatype.
-- The conversion will fail if the value is outside the valid range of
-- a Haskell `Float`.
instance FromRDFLabel Float where
fromRDFLabel = fLabel textToFloat xsdFloat
-- | Converts to a literal with a @xsd:double@ datatype.
instance ToRDFLabel Double where
toRDFLabel = fromRealFloat xsdDouble
-- | Converts from a literal with a @xsd:double@ datatype.
instance FromRDFLabel Double where
fromRDFLabel = fLabel textToDouble xsdDouble
-- TODO: are there subtypes of xsd::integer that are
-- useful here?
--
-- TODO: add in support for Int8/..., Word8/...
--
-- | Converts to a literal with a @xsd:integer@ datatype.
instance ToRDFLabel Int where
toRDFLabel = tLabel xsdInteger T.pack
{-
Since decimal will just over/under-flow when converting to Int
we go via Integer and explicitly check for overflow.
-}
textToInt :: T.Text -> Maybe Int
textToInt s =
let conv :: Integer -> Maybe Int
conv i =
let lb = fromIntegral (minBound :: Int)
ub = fromIntegral (maxBound :: Int)
in if (i >= lb) && (i <= ub) then Just (fromIntegral i) else Nothing
in maybeRead (T.signed T.decimal) s >>= conv
-- | Converts from a literal with a @xsd:integer@ datatype.
-- The conversion will fail if the value is outside the valid range of
-- a Haskell `Int`.
instance FromRDFLabel Int where
fromRDFLabel = fLabel textToInt xsdInteger
-- | Converts to a literal with a @xsd:integer@ datatype.
instance ToRDFLabel Integer where
toRDFLabel = tLabel xsdInteger T.pack
-- | Converts from a literal with a @xsd:integer@ datatype.
instance FromRDFLabel Integer where
fromRDFLabel = fLabel (maybeRead (T.signed T.decimal)) xsdInteger
{-
Support an ISO-8601 style format supporting
2005-02-28T00:00:00Z
2004-12-31T19:01:00-05:00
2005-07-14T03:18:56.234+01:00
fromUTCFormat is used to convert UTCTime to a string
for storage within a Lit.
toUTCFormat is used to convert a string into UTCTime;
we have to support
no time zone
Z
+/-HH:MM
which means a somewhat messy convertor, which is written
for clarity rather than speed.
-}
fromUTCFormat :: UTCTime -> String
fromUTCFormat = formatTime defaultTimeLocale "%FT%T%QZ"
fromDayFormat :: Day -> String
fromDayFormat = formatTime defaultTimeLocale "%FZ"
toTimeFormat :: (ParseTime a) => String -> String -> Maybe a
toTimeFormat fmt inVal =
let fmtHHMM = fmt ++ "%z"
fmtZ = fmt ++ "Z"
#if MIN_VERSION_time(1,5,0)
pt f = parseTimeM True defaultTimeLocale f inVal
#else
pt f = parseTime defaultTimeLocale f inVal
#endif
in case pt fmtHHMM of
o@(Just _) -> o
_ -> case pt fmtZ of
o@(Just _) -> o
_ -> pt fmt
toUTCFormat :: T.Text -> Maybe UTCTime
toUTCFormat = toTimeFormat "%FT%T%Q" . T.unpack
toDayFormat :: T.Text -> Maybe Day
toDayFormat = toTimeFormat "%F" . T.unpack
-- | Converts to a literal with a @xsd:datetime@ datatype.
instance ToRDFLabel UTCTime where
toRDFLabel = flip TypedLit xsdDateTime . T.pack . fromUTCFormat
-- | Converts from a literal with a @xsd:datetime@ datatype.
instance FromRDFLabel UTCTime where
fromRDFLabel = fLabel toUTCFormat xsdDateTime
-- | Converts to a literal with a @xsd:date@ datatype.
instance ToRDFLabel Day where
toRDFLabel = flip TypedLit xsdDate . T.pack . fromDayFormat
-- | Converts from a literal with a @xsd:date@ datatype.
instance FromRDFLabel Day where
fromRDFLabel = fLabel toDayFormat xsdDate
-- | Converts to a Resource.
instance ToRDFLabel ScopedName where
toRDFLabel = Res
-- | Converts from a Resource.
instance FromRDFLabel ScopedName where
fromRDFLabel (Res sn) = Just sn
fromRDFLabel _ = Nothing
-- | Converts to a Resource.
instance ToRDFLabel QName where
toRDFLabel = Res . makeQNameScopedName Nothing
-- | Converts from a Resource.
instance FromRDFLabel QName where
fromRDFLabel (Res sn) = Just $ getQName sn
fromRDFLabel _ = Nothing
-- | Converts to a Resource.
instance ToRDFLabel URI where
toRDFLabel = Res . makeURIScopedName
-- | Converts from a Resource.
instance FromRDFLabel URI where
fromRDFLabel (Res sn) = Just $ getScopedNameURI sn
fromRDFLabel _ = Nothing
-- | Get the canonical string for RDF label.
--
-- This is used for hashing, so that equivalent labels always return
-- the same hash value.
--
-- TODO: can remove the use of quote1Str as all we care about is
-- a unique output, not that it is valid in any RDF format. Also
-- rename to showForHash or something like that, since it is only used
-- for this purpose.
--
showCanon :: RDFLabel -> String
showCanon (Res sn) = "<" ++ show (getScopedNameURI sn) ++ ">"
showCanon (Lit st) = show st
showCanon (LangLit st lang) = quote1Str st ++ "@" ++ T.unpack (fromLangTag lang)
showCanon (TypedLit st dt) = quote1Str st ++ "^^" ++ show (getScopedNameURI dt)
showCanon s = show s
-- The Data.Text API points out issues with processing a text
-- character-by-character, but it's not clear to me how to avoid
-- that here.
--
-- One assumption would be that the strings aren't likely to be large,
-- so that several calls to T.find or similar could be made to
-- simplify certain cases.
--
-- Is it worth scanning through the text to look for characters like \n
-- or #, or to look for sequences like '##'?
-- Is it worth sending in a flag to indicate the different modes for
-- handling \n characters, or just leave this complexity in 'quoteT False'?
--
processChar ::
Char
-> (T.Text, Bool) -- ^ the boolean is @True@ if the returned text has been
-- expanded so that it begins with @\\@
processChar '"' = ("\\\"", True)
processChar '\\' = ("\\\\", True)
processChar '\n' = ("\\n", True)
processChar '\r' = ("\\r", True)
processChar '\t' = ("\\t", True)
processChar '\b' = ("\\b", True)
-- processChar '\f' = ("\\f", True)
-- Using the above I get invalid output according to
-- rapper version 2.0.9, so use the following for now
-- (changed at version 0.9.0.6)
processChar '\f' = ("\\u000C", True) --
processChar c =
let nc = ord c
-- lazy ways to convert to hex-encoded strings
four = T.append "\\u" . T.pack $ printf "%04X" nc
eight = T.append "\\U" . T.pack $ printf "%08X" nc
in if nc < 0x20
then (four, True)
else if nc < 0x7f
then (T.singleton c, False)
else if nc < 0x10000
then (four, True)
else (eight, True)
convertChar :: Char -> T.Text
convertChar = fst . processChar
-- | See `quote`.
quoteT :: Bool -> T.Text -> T.Text
quoteT True txt =
-- Output is to be used as "..."
let go dl x =
case T.uncons x of
Just (c, xs) -> go (dl . T.append (convertChar c)) xs
_ -> dl T.empty
in go (T.append T.empty) txt
-- One complexity here is my reading of the Turtle grammar
-- STRING_LITERAL_LONG_QUOTE ::= '"""' (('"' | '""')? [^"\] | ECHAR | UCHAR)* '"""'
-- which says that any un-protected double-quote characters can not
-- be followed by a \ character. One option would be to always use the
-- 'quoteT True' behavior.
--
quoteT _ txt =
-- Output is to be used as """...""""
let go dl x =
case T.uncons x of
Just ('"', xs) -> go1 dl xs
Just ('\n', xs) -> go (dl . T.cons '\n') xs
Just (c, xs) -> go (dl . T.append (convertChar c)) xs
_ -> dl T.empty
-- Seen one double quote
go1 dl x =
case T.uncons x of
Just ('"', xs) -> go2 dl xs
Just ('\n', xs) -> go (dl . T.append "\"\n") xs
Just ('\\', xs) -> go (dl . T.append "\\\"\\\\") xs
Just (c, xs) ->
let (t, f) = processChar c
dl' = if f then T.append "\\\"" else T.cons '"'
in go (dl . dl' . T.append t) xs
_ -> dl "\\\""
-- Seen two double quotes
go2 dl x =
case T.uncons x of
Just ('"', xs) -> go (dl . T.append "\\\"\\\"\\\"") xs
Just ('\n', xs) -> go (dl . T.append "\"\"\n") xs
Just ('\\', xs) -> go (dl . T.append "\\\"\\\"\\\\") xs
Just (c, xs) ->
let (t, f) = processChar c
dl' = T.append (if f then "\\\"\\\"" else "\"\"")
in go (dl . dl' . T.append t) xs
_ -> dl "\\\"\\\""
-- at the start of the string we have 3 quotes, so any
-- starting quote characters must be quoted.
go0 dl x =
case T.uncons x of
Just ('"', xs) -> go0 (dl . T.append "\\\"") xs
Just ('\n', xs) -> go (dl . T.cons '\n') xs
Just (c, xs) -> go (dl . T.append (convertChar c)) xs
_ -> dl T.empty
in go0 (T.append T.empty) txt
-- | Turtle-style quoting rules for a string.
--
-- At present the choice is between using one or three
-- double quote (@\"@) characters to surround the string; i.e. using
-- single quote (@'@) characters is not supported.
--
-- As of Swish 0.9.0.6, the @\\f@ character is converted to
-- @\\u000C@ rather than left as is to aid interoperability
-- with some other tools.
--
quote ::
Bool -- ^ @True@ if the string is to be displayed using one rather than three quotes.
-> String -- ^ String to quote.
-> String -- ^ The string does /not/ contain the surrounding quote marks.
quote f = T.unpack . quoteT f . T.pack
{-
quote _ [] = ""
quote False s@(c:'"':[]) | c == '\\' = s -- handle triple-quoted strings ending in "
| otherwise = [c, '\\', '"']
quote True ('"': st) = '\\':'"': quote True st
quote True ('\n':st) = '\\':'n': quote True st
quote True ('\t':st) = '\\':'t': quote True st
quote False ('"': st) = '"': quote False st
quote False ('\n':st) = '\n': quote False st
quote False ('\t':st) = '\t': quote False st
quote f ('\r':st) = '\\':'r': quote f st
quote f ('\\':st) = '\\':'\\': quote f st -- not sure about this
quote f (c:st) =
let nc = ord c
rst = quote f st
-- lazy way to convert to a string
hstr = printf "%08X" nc
ustr = hstr ++ rst
in if nc > 0xffff
then '\\':'U': ustr
else if nc > 0x7e || nc < 0x20
then '\\':'u': drop 4 ustr
else c : rst
-}
-- surround a string with a single double-quote mark at each end,
-- e.g. "...".
quote1Str :: T.Text -> String
quote1Str t = '"' : T.unpack (quoteT True t) ++ "\""
---------------------------------------------------------
-- Selected RDFLabel values
---------------------------------------------------------
-- | @rdf:type@ from .
resRdfType :: RDFLabel
resRdfType = Res rdfType
-- | @rdf:List@ from .
resRdfList :: RDFLabel
resRdfList = Res rdfList
-- | @rdf:first@ from .
resRdfFirst :: RDFLabel
resRdfFirst = Res rdfFirst
-- | @rdf:rest@ from .
resRdfRest :: RDFLabel
resRdfRest = Res rdfRest
-- | @rdf:nil@ from .
resRdfNil :: RDFLabel
resRdfNil = Res rdfNil
-- | @rdfs:member@ from .
resRdfsMember :: RDFLabel
resRdfsMember = Res rdfsMember
-- | @rdfd:GeneralRestriction@.
resRdfdGeneralRestriction :: RDFLabel
resRdfdGeneralRestriction = Res rdfdGeneralRestriction
-- | @rdfd:onProperties@.
resRdfdOnProperties :: RDFLabel
resRdfdOnProperties = Res rdfdOnProperties
-- | @rdfd:constraint@.
resRdfdConstraint :: RDFLabel
resRdfdConstraint = Res rdfdConstraint
-- | @rdfd:maxCardinality@.
resRdfdMaxCardinality :: RDFLabel
resRdfdMaxCardinality = Res rdfdMaxCardinality
-- | @rdfs:seeAlso@ from .
resRdfsSeeAlso :: RDFLabel
resRdfsSeeAlso = Res rdfsSeeAlso
-- | @rdf:value@ from .
resRdfValue :: RDFLabel
resRdfValue = Res rdfValue
-- | @owl:sameAs@.
resOwlSameAs :: RDFLabel
resOwlSameAs = Res owlSameAs
-- | @log:implies@.
resLogImplies :: RDFLabel
resLogImplies = Res logImplies
-- | @rdfs:label@ from .
resRdfsLabel :: RDFLabel
resRdfsLabel = Res rdfsLabel
-- | @rdfs:comment@ from .
resRdfsComment :: RDFLabel
resRdfsComment = Res rdfsComment
-- | @rdf:Property@ from .
resRdfProperty :: RDFLabel
resRdfProperty = Res rdfProperty
-- | @rdfs:subPropertyOf@ from .
resRdfsSubPropertyOf :: RDFLabel
resRdfsSubPropertyOf = Res rdfsSubPropertyOf
-- | @rdfs:subClassOf@ from .
resRdfsSubClassOf :: RDFLabel
resRdfsSubClassOf = Res rdfsSubClassOf
-- | @rdfs:Class@ from .
resRdfsClass :: RDFLabel
resRdfsClass = Res rdfsClass
-- | @rdfs:Literal@ from .
resRdfsLiteral :: RDFLabel
resRdfsLiteral = Res rdfsLiteral
-- | @rdfs:Datatype@ from .
resRdfsDatatype :: RDFLabel
resRdfsDatatype = Res rdfsDatatype
-- | @rdf:XMLLiteral@ from .
resRdfXMLLiteral :: RDFLabel
resRdfXMLLiteral = Res rdfXMLLiteral
-- | @rdfs:range@ from .
resRdfsRange :: RDFLabel
resRdfsRange = Res rdfsRange
-- | @rdfs:domain@ from .
resRdfsDomain :: RDFLabel
resRdfsDomain = Res rdfsDomain
-- | @rdfs:Container@ from .
resRdfsContainer :: RDFLabel
resRdfsContainer = Res rdfsContainer
-- | @rdf:Bag@ from .
resRdfBag :: RDFLabel
resRdfBag = Res rdfBag
-- | @rdf:Seq@ from .
resRdfSeq :: RDFLabel
resRdfSeq = Res rdfSeq
-- | @rdf:Alt@ from .
resRdfAlt :: RDFLabel
resRdfAlt = Res rdfAlt
-- | @rdfs:ContainerMembershipProperty@ from .
resRdfsContainerMembershipProperty :: RDFLabel
resRdfsContainerMembershipProperty = Res rdfsContainerMembershipProperty
-- | @rdfs:isDefinedBy@ from .
resRdfsIsDefinedBy :: RDFLabel
resRdfsIsDefinedBy = Res rdfsIsDefinedBy
-- | @rdfs:Resource@ from .
resRdfsResource :: RDFLabel
resRdfsResource = Res rdfsResource
-- | @rdf:Statement@ from .
resRdfStatement :: RDFLabel
resRdfStatement = Res rdfStatement
-- | @rdf:subject@ from .
resRdfSubject :: RDFLabel
resRdfSubject = Res rdfSubject
-- | @rdf:predicate@ from .
resRdfPredicate :: RDFLabel
resRdfPredicate = Res rdfPredicate
-- | @rdf:object@ from .
resRdfObject :: RDFLabel
resRdfObject = Res rdfObject
-- | @rdf:RDF@.
resRdfRDF :: RDFLabel
resRdfRDF = Res rdfRDF
-- | @rdf:Description@.
resRdfDescription :: RDFLabel
resRdfDescription = Res rdfDescription
-- | @rdf:ID@.
resRdfID :: RDFLabel
resRdfID = Res rdfID
-- | @rdf:about@.
resRdfAbout :: RDFLabel
resRdfAbout = Res rdfAbout
-- | @rdf:parseType@.
resRdfParseType :: RDFLabel
resRdfParseType = Res rdfParseType
-- | @rdf:resource@.
resRdfResource :: RDFLabel
resRdfResource = Res rdfResource
-- | @rdf:li@.
resRdfLi :: RDFLabel
resRdfLi = Res rdfLi
-- | @rdf:nodeID@.
resRdfNodeID :: RDFLabel
resRdfNodeID = Res rdfNodeID
-- | @rdf:datatype@.
resRdfDatatype :: RDFLabel
resRdfDatatype = Res rdfDatatype
-- | @rdf:_1@.
resRdf1 :: RDFLabel
resRdf1 = Res rdf1
-- | @rdf:_2@.
resRdf2 :: RDFLabel
resRdf2 = Res rdf2
-- | Create a @rdf:_n@ entity.
--
-- There is no check that the argument is not @0@.
resRdfn :: Word32 -> RDFLabel
resRdfn = Res . rdfn
---------------------------------------------------------
-- Additional functions on RDFLabel values
---------------------------------------------------------
-- |Test if supplied labal is a URI resource node
isUri :: RDFLabel -> Bool
isUri (Res _) = True
isUri _ = False
-- |Test if supplied labal is a literal node
-- ('Lit', 'LangLit', or 'TypedLit').
isLiteral :: RDFLabel -> Bool
isLiteral (Lit _) = True
isLiteral (LangLit _ _) = True
isLiteral (TypedLit _ _) = True
isLiteral _ = False
-- |Test if supplied labal is an untyped literal node (either
-- 'Lit' or 'LangLit').
isUntypedLiteral :: RDFLabel -> Bool
isUntypedLiteral (Lit _) = True
isUntypedLiteral (LangLit _ _) = True
isUntypedLiteral _ = False
-- |Test if supplied labal is a typed literal node ('TypedLit').
isTypedLiteral :: RDFLabel -> Bool
isTypedLiteral (TypedLit _ _) = True
isTypedLiteral _ = False
-- |Test if supplied labal is a XML literal node
isXMLLiteral :: RDFLabel -> Bool
isXMLLiteral = isDatatyped rdfXMLLiteral
-- |Test if supplied label is a typed literal node of a given datatype
isDatatyped :: ScopedName -> RDFLabel -> Bool
isDatatyped d (TypedLit _ dt) = d == dt
isDatatyped _ _ = False
-- |Test if supplied label is a container membership property
--
-- Check for namespace is RDF namespace and
-- first character of local name is '_' and
-- remaining characters of local name are all digits
isMemberProp :: RDFLabel -> Bool
isMemberProp (Res sn) =
getScopeNamespace sn == namespaceRDF &&
case T.uncons (getLName (getScopeLocal sn)) of
Just ('_', t) -> T.all isDigit t
_ -> False
isMemberProp _ = False
-- |Test if supplied labal is a blank node
isBlank :: RDFLabel -> Bool
isBlank (Blank _) = True
isBlank _ = False
-- |Test if supplied labal is a query variable
isQueryVar :: RDFLabel -> Bool
isQueryVar (Var _) = True
isQueryVar _ = False
-- |Extract text value from a literal node (including the
-- Language and Typed variants). The empty string is returned
-- for other nodes.
getLiteralText :: RDFLabel -> T.Text
getLiteralText (Lit s) = s
getLiteralText (LangLit s _) = s
getLiteralText (TypedLit s _) = s
getLiteralText _ = ""
-- |Extract the ScopedName value from a resource node ('nullScopedName'
-- is returned for non-resource nodes).
getScopedName :: RDFLabel -> ScopedName
getScopedName (Res sn) = sn
getScopedName _ = nullScopedName
-- |Make a blank node from a supplied query variable,
-- or return the supplied label unchanged.
-- (Use this in when substituting an existential for an
-- unsubstituted query variable.)
makeBlank :: RDFLabel -> RDFLabel
makeBlank (Var loc) = Blank loc
makeBlank lb = lb
-- | RDF Triple (statement)
--
-- At present there is no check or type-level
-- constraint that stops the subject or
-- predicate of the triple from being a literal.
--
type RDFTriple = Arc RDFLabel
-- | A set of RDF triples.
type RDFArcSet = ArcSet RDFLabel
-- | Convert 3 RDF labels to a RDF triple.
--
-- See also @Swish.RDF.GraphClass.arcFromTriple@.
toRDFTriple ::
(ToRDFLabel s, ToRDFLabel p, ToRDFLabel o)
=> s -- ^ Subject
-> p -- ^ Predicate
-> o -- ^ Object
-> RDFTriple
toRDFTriple s p o =
Arc (toRDFLabel s) (toRDFLabel p) (toRDFLabel o)
-- | Extract the contents of a RDF triple.
--
-- See also @Swish.RDF.GraphClass.arcToTriple@.
fromRDFTriple ::
(FromRDFLabel s, FromRDFLabel p, FromRDFLabel o)
=> RDFTriple
-> Maybe (s, p, o) -- ^ The conversion only succeeds if all three
-- components can be converted to the correct
-- Haskell types.
fromRDFTriple (Arc s p o) =
(,,) <$> fromRDFLabel s <*> fromRDFLabel p <*> fromRDFLabel o
-- | Namespace prefix list entry
-- | A map for name spaces (key is the prefix).
type NamespaceMap = M.Map (Maybe T.Text) URI -- TODO: should val be URI or namespace?
-- | Create an empty namespace map.
emptyNamespaceMap :: NamespaceMap
emptyNamespaceMap = M.empty
-- | Graph formula entry
data LookupFormula lb gr = Formula
{ formLabel :: lb -- ^ The label for the formula
, formGraph :: gr -- ^ The contents of the formula
}
instance (Eq lb, Eq gr) => Eq (LookupFormula lb gr) where
f1 == f2 = formLabel f1 == formLabel f2 &&
formGraph f1 == formGraph f2
instance (Ord lb, Ord gr) => Ord (LookupFormula lb gr) where
(Formula a1 b1) `compare` (Formula a2 b2) =
(a1,b1) `compare` (a2,b2)
-- | A named formula.
type Formula lb = LookupFormula lb (NSGraph lb)
instance (Label lb) => Show (Formula lb)
where
show (Formula l g) = show l ++ " :- { " ++ showArcs " " g ++ " }"
-- | A map for named formulae.
type FormulaMap lb = M.Map lb (NSGraph lb)
-- | Create an empty formula map.
emptyFormulaMap :: FormulaMap RDFLabel
emptyFormulaMap = M.empty
-- fmapFormulaMap :: (Ord a, Ord b) => (a -> b) -> FormulaMap a -> FormulaMap b
fmapFormulaMap :: (Ord a) => (a -> a) -> FormulaMap a -> FormulaMap a
fmapFormulaMap f m = M.fromList $ map (f *** fmapNSGraph f) $ M.assocs m
-- TODO: how to traverse formulamaps now?
{-
traverseFormulaMap ::
(Applicative f, Ord a, Ord b)
=> (a -> f b) -> FormulaMap a -> f (FormulaMap b)
-}
traverseFormulaMap ::
(Applicative f, Ord a)
=> (a -> f a) -> FormulaMap a -> f (FormulaMap a)
traverseFormulaMap f = Traversable.traverse (traverseFormula f)
{-
traverseFormula ::
(Applicative f, Ord a, Ord b)
=> (a -> f b) -> Formula a -> f (Formula b)
-}
{-
traverseFormula ::
(Applicative f, Ord a)
=> (a -> f a) -> Formula a -> f (Formula a)
traverseFormula f (Formula k gr) = Formula <$> f k <*> traverseNSGraph f gr
-}
traverseFormula ::
(Applicative f, Ord a)
=> (a -> f a) -> NSGraph a -> f (NSGraph a)
{-
traverseFormula ::
(Applicative f, Ord a, Ord b)
=> (a -> f b) -> NSGraph a -> f (NSGraph b)
-}
traverseFormula = traverseNSGraph
{-
formulaeMapM ::
(Monad m) => (lb -> m l2) -> FormulaMap lb -> m (FormulaMap l2)
formulaeMapM f = T.mapM (formulaEntryMapM f)
formulaEntryMapM ::
(Monad m)
=> (lb -> m l2)
-> Formula lb
-> m (Formula l2)
formulaEntryMapM f (Formula k gr) =
Formula `liftM` f k `ap` T.mapM f gr
-}
{-|
Memory-based graph with namespaces and subgraphs.
The primary means for adding arcs to an existing graph
are:
- `setArcs` from the `LDGraph` instance, which replaces the
existing set of arcs and does not change the namespace
map.
- `addArc` which checks that the arc is unknown before
adding it but does not change the namespace map or
re-label any blank nodes in the arc.
-}
data NSGraph lb = NSGraph
{ namespaces :: NamespaceMap -- ^ the namespaces to use
, formulae :: FormulaMap lb -- ^ any associated formulae
-- (a.k.a. sub- or named- graps)
, statements :: ArcSet lb -- ^ the statements in the graph
}
instance LDGraph NSGraph lb where
emptyGraph = NSGraph emptyNamespaceMap M.empty S.empty
getArcs = statements
setArcs g as = g { statements=as }
-- | The '<>' operation uses 'merge' rather than 'addGraphs'.
instance (Label lb) => Semigroup (NSGraph lb) where
(<>) = merge
-- | The 'mappend' operation uses the Semigroup instance
-- (so 'merge' rather than 'addGraphs').
instance (Label lb) => Monoid (NSGraph lb) where
mempty = emptyGraph
#if !(MIN_VERSION_base(4, 11, 0))
mappend = (<>)
#endif
-- fmapNSGraph :: (Ord lb1, Ord lb2) => (lb1 -> lb2) -> NSGraph lb1 -> NSGraph lb2
-- | 'fmap' for 'NSGraph' instances.
fmapNSGraph :: (Ord lb) => (lb -> lb) -> NSGraph lb -> NSGraph lb
fmapNSGraph f (NSGraph ns fml stmts) =
NSGraph ns (fmapFormulaMap f fml) ((S.map $ fmap f) stmts)
{-
traverseNSGraph ::
(Applicative f, Ord a, Ord b)
=> (a -> f b) -> NSGraph a -> f (NSGraph b)
-}
-- | 'Data.Traversable.traverse' for 'NSGraph' instances.
traverseNSGraph ::
(Applicative f, Ord a)
=> (a -> f a) -> NSGraph a -> f (NSGraph a)
traverseNSGraph f (NSGraph ns fml stmts) =
NSGraph ns <$> traverseFormulaMap f fml <*> (traverseSet $ Traversable.traverse f) stmts
traverseSet ::
(Applicative f, Ord b)
=> (a -> f b) -> S.Set a -> f (S.Set b)
traverseSet f = S.foldr cons (pure S.empty)
where
cons x s = S.insert <$> f x <*> s
instance (Label lb) => Eq (NSGraph lb) where
(==) = grEq
-- The namespaces are not used in the ordering since this could
-- lead to identical graphs not being considered the same when
-- ordering.
--
instance (Label lb) => Ord (NSGraph lb) where
(NSGraph _ fml1 stmts1) `compare` (NSGraph _ fml2 stmts2) =
(fml1,stmts1) `compare` (fml2,stmts2)
instance (Label lb) => Show (NSGraph lb) where
show = grShow ""
showList = grShowList ""
-- | Retrieve the namespace map in the graph.
getNamespaces :: NSGraph lb -> NamespaceMap
getNamespaces = namespaces
-- | Replace the namespace information in the graph.
setNamespaces :: NamespaceMap -> NSGraph lb -> NSGraph lb
setNamespaces ns g = g { namespaces=ns }
-- | Retrieve the formulae in the graph.
getFormulae :: NSGraph lb -> FormulaMap lb
getFormulae = formulae
-- | Replace the formulae in the graph.
setFormulae :: FormulaMap lb -> NSGraph lb -> NSGraph lb
setFormulae fs g = g { formulae=fs }
-- | Find a formula in the graph, if it exists.
getFormula :: (Label lb) => NSGraph lb -> lb -> Maybe (NSGraph lb)
-- getFormula g l = fmap formGraph $ M.lookup l (formulae g)
getFormula g l = M.lookup l (formulae g)
-- | Add (or replace) a formula.
setFormula :: (Label lb) => Formula lb -> NSGraph lb -> NSGraph lb
-- setFormula f g = g { formulae = M.insert (formLabel f) f (formulae g) }
setFormula (Formula fn fg) g = g { formulae = M.insert fn fg (formulae g) }
{-|
Add an arc to the graph. It does not relabel any blank nodes in the input arc,
nor does it change the namespace map,
but it does ensure that the arc is unknown before adding it.
-}
addArc :: (Label lb) => Arc lb -> NSGraph lb -> NSGraph lb
addArc ar = update (S.insert ar)
grShowList :: (Label lb) => String -> [NSGraph lb] -> String -> String
grShowList _ [] = showString "[no graphs]"
grShowList p (g:gs) = showChar '[' . showString (grShow pp g) . showl gs
where
showl [] = showChar ']' -- showString $ "\n" ++ p ++ "]"
showl (h:hs) = showString (",\n " ++ p ++ grShow pp h) . showl hs
pp = ' ':p
grShow :: (Label lb) => String -> NSGraph lb -> String
grShow p g =
"Graph, formulae: " ++ showForm ++ "\n" ++
p ++ "arcs: " ++ showArcs p g
where
showForm = concatMap ((pp ++) . show) fml
fml = map (uncurry Formula) $ M.assocs (getFormulae g) -- NOTE: want to just show 'name :- graph'
pp = "\n " ++ p
showArcs :: (Label lb) => String -> NSGraph lb -> String
showArcs p g = S.foldr ((++) . (pp ++) . show) "" (getArcs g)
where
pp = "\n " ++ p
-- | Graph equality.
grEq :: (Label lb) => NSGraph lb -> NSGraph lb -> Bool
grEq g1 = fst . grMatchMap g1
-- | Match graphs, returning `True` if they are equivalent,
-- with a map of labels to equivalence class identifiers
-- (see 'graphMatch' for further details).
grMatchMap :: (Label lb) =>
NSGraph lb -> NSGraph lb -> (Bool, LabelMap (ScopedLabel lb))
grMatchMap g1 g2 =
graphMatch matchable (getArcs g1) (getArcs g2)
where
matchable l1 l2 = mapFormula g1 l1 == mapFormula g2 l2
-- hmmm, if we compare the formula, rather then graph,
-- a lot of tests fail (when the formulae are named by blank
-- nodes). Presumably because the quality check for Formula forces
-- the label to be identical, which it needn't be with bnodes
-- for the match to hold.
-- mapFormula g l = M.lookup l (getFormulae g)
-- mapFormula g l = fmap formGraph $ M.lookup l (getFormulae g)
-- the above discussion is hopefully moot now storing graph directly
mapFormula g l = M.lookup l (getFormulae g)
-- |Merge RDF graphs, renaming blank and query variable nodes as
-- needed to neep variable nodes from the two graphs distinct in
-- the resulting graph.
--
-- Currently formulae are not guaranteed to be preserved across a
-- merge.
--
merge :: (Label lb) => NSGraph lb -> NSGraph lb -> NSGraph lb
merge gr1 gr2 =
let bn1 = S.toList $ allLabels labelIsVar gr1
bn2 = S.toList $ allLabels labelIsVar gr2
dupbn = intersect bn1 bn2
allbn = union bn1 bn2
in addGraphs gr1 (remapLabels dupbn allbn id gr2)
-- |Return list of all labels (including properties) in the graph
-- satisfying a supplied filter predicate. This routine
-- includes the labels in any formulae.
allLabels :: (Label lb) => (lb -> Bool) -> NSGraph lb -> S.Set lb
allLabels p gr = S.filter p (unionNodes p (formulaNodes p gr) (labels gr) )
{- TODO: is the leading 'filter p' needed in allLabels?
-}
-- |Return list of all subjects and objects in the graph
-- satisfying a supplied filter predicate.
allNodes :: (Label lb) => (lb -> Bool) -> NSGraph lb -> S.Set lb
allNodes p = unionNodes p S.empty . nodes
-- | List all nodes in graph formulae satisfying a supplied predicate
formulaNodes :: (Label lb) => (lb -> Bool) -> NSGraph lb -> S.Set lb
formulaNodes p gr = foldl' (unionNodes p) fkeys (map (allLabels p) fvals)
where
fm = formulae gr
-- fvals = map formGraph $ M.elems fm
fvals = M.elems fm
-- TODO: can this conversion be improved?
fkeys = S.filter p $ S.fromList $ M.keys fm
-- | Helper to filter variable nodes and merge with those found so far
unionNodes :: (Label lb) => (lb -> Bool) -> S.Set lb -> S.Set lb -> S.Set lb
unionNodes p ls1 ls2 = ls1 `S.union` S.filter p ls2
-- TODO: use S.Set lb rather than [lb] in the following
-- |Remap selected nodes in graph.
--
-- This is the node renaming operation that prevents graph-scoped
-- variable nodes from being merged when two graphs are merged.
remapLabels ::
(Label lb)
=> [lb] -- ^ variable nodes to be renamed (@dupbn@)
-> [lb] -- ^ variable nodes used that must be avoided (@allbn@)
-> (lb -> lb) -- ^ node conversion function that is applied to nodes
-- from @dupbn@ in the graph that are to be replaced by
-- new blank nodes. If no such conversion is required,
-- supply @id@. The function 'makeBlank' can be used to convert
-- RDF query nodes into RDF blank nodes.
-> NSGraph lb -- ^ graph in which nodes are to be renamed
-> NSGraph lb
remapLabels dupbn allbn cnvbn =
fmapNSGraph (mapnode dupbn allbn cnvbn)
-- |Externally callable function to construct a list of (old,new)
-- values to be used for graph label remapping.
--
remapLabelList ::
(Label lb)
=> [lb] -- ^ labels to be remaped
-> [lb] -- ^ labels to be avoided by the remapping
-> [(lb,lb)]
remapLabelList remap avoid = maplist remap avoid id []
-- | Remap a single graph node.
--
-- If the node is not one of those to be remapped,
-- the supplied value is returned unchanged.
mapnode ::
(Label lb) => [lb] -> [lb] -> (lb -> lb) -> lb -> lb
mapnode dupbn allbn cnvbn nv =
M.findWithDefault nv nv $ M.fromList $ maplist dupbn allbn cnvbn []
-- | Construct a list of (oldnode,newnode) values to be used for
-- graph label remapping. The function operates recursively, adding
-- new nodes generated to the accumulator and also to the
-- list of nodes to be avoided.
maplist ::
(Label lb)
=> [lb] -- ^ oldnode values
-> [lb] -- ^ nodes to be avoided
-> (lb -> lb)
-> [(lb,lb)] -- ^ accumulator
-> [(lb,lb)]
maplist [] _ _ mapbn = mapbn
maplist (dn:dupbn) allbn cnvbn mapbn = maplist dupbn allbn' cnvbn mapbn'
where
dnmap = newNode (cnvbn dn) allbn
mapbn' = (dn,dnmap):mapbn
allbn' = dnmap:allbn
-- TODO: optimize this for common case @nnn@ and @_nnn@:
-- always generate @_nnn@ and keep track of last allocated
--
-- |Given a node and a list of existing nodes, find a new node for
-- the supplied node that does not clash with any existing node.
-- (Generates an non-terminating list of possible replacements, and
-- picks the first one that isn't already in use.)
--
newNode :: (Label lb) => lb -> [lb] -> lb
newNode dn existnodes =
head $ newNodes dn existnodes
-- |Given a node and a list of existing nodes, generate a list of new
-- nodes for the supplied node that do not clash with any existing node.
newNodes :: (Label lb) => lb -> [lb] -> [lb]
newNodes dn existnodes =
filter (not . (`elem` existnodes)) $ trynodes (noderootindex dn)
{-
For now go with a 32-bit integer (since Int on my machine uses 32-bit
values). We could instead use a Whole class constraint from
Numeric.Natural (in semigroups), but it is probably better to
specialize here. The idea for using Word rather than Int is to
make it obvious that we are only interested in values >= 0.
-}
noderootindex :: (Label lb) => lb -> (String, Word32)
noderootindex dn = (nh,nx) where
(nh,nt) = splitnodeid $ getLocal dn
nx = if null nt then 0 else read nt
splitnodeid :: String -> (String,String)
splitnodeid = break isDigit
trynodes :: (Label lb) => (String, Word32) -> [lb]
trynodes (nr,nx) = [ makeLabel (nr ++ show n) | n <- iterate (+ 1) nx ]
{-
trybnodes :: (Label lb) => (String,Int) -> [lb]
trybnodes (nr,nx) = [ makeLabel (nr++show n) | n <- iterate (+1) nx ]
-}
-- | Memory-based RDF graph type
type RDFGraph = NSGraph RDFLabel
-- |Create a new RDF graph from a supplied set of arcs.
--
-- This version will attempt to fill up the namespace map
-- of the graph based on the input labels (including datatypes
-- on literals). For faster
-- creation of a graph you can use:
--
-- > emptyRDFGraph { statements = arcs }
--
-- which is how this routine was defined in version @0.3.1.1@
-- and earlier.
--
toRDFGraph ::
RDFArcSet
-> RDFGraph
toRDFGraph arcs =
let lbls = getComponents arcLabels arcs
getNS (Res s) = Just s
getNS (TypedLit _ dt) = Just dt
getNS _ = Nothing
ns = mapMaybe (fmap getScopeNamespace . getNS) $ S.toList lbls
nsmap = foldl'
(\m ins -> let (p,u) = getNamespaceTuple ins
in M.insertWith (const id) p u m)
emptyNamespaceMap ns
in mempty { namespaces = nsmap, statements = arcs }
-- |Create a new, empty RDF graph (it is just 'mempty').
--
emptyRDFGraph :: RDFGraph
emptyRDFGraph = mempty
{-
-- |Update an RDF graph using a supplied list of arcs, keeping
-- prefix definitions and formula definitions from the original.
--
-- [[[TODO: I think this may be redundant - the default graph
-- class has an update method which accepts a function to update
-- the arcs, not touching other parts of the graph value.]]]
updateRDFGraph :: RDFGraph -> [RDFTriple] -> RDFGraph
updateRDFGraph gr as = gr { statements=as }
-}
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012, 2013, 2014, 2015, 2016, 2018, 2020, 2022 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/RDF/GraphShowLines.hs 0000644 0000000 0000000 00000004363 13767237337 017035 0 ustar 00 0000000 0000000 {-# LANGUAGE FlexibleInstances #-}
--------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : GraphShowLines
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011, 2012, 2020 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : FlexibleInstances, TypeSynonymInstances
--
-- This module defines a `ShowLines` class instance for `RDFGraph`, to be
-- used when displaying RDF Graph values as part of a proof sequence,
-- etc.
--
--------------------------------------------------------------------------------
module Swish.RDF.GraphShowLines () where
import Swish.RDF.Graph (RDFGraph)
import Swish.RDF.Formatter.N3 (formatGraphIndent)
import Data.String.ShowLines (ShowLines(..))
-- import qualified Data.Text.Lazy as L
import qualified Data.Text.Lazy.Builder as B
instance ShowLines RDFGraph where
-- showls linebreak = shows . L.unpack . B.toLazyText . formatGraphIndent linebreak False
showls linebreak = shows . formatGraphIndent (B.fromString linebreak) False
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012, 2020 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/RDF/Parser/NTriples.hs 0000644 0000000 0000000 00000026645 14066415574 017135 0 ustar 00 0000000 0000000 {-# LANGUAGE CPP #-}
--------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : NTriples
-- Copyright : (c) 2011, 2012, 2013, 2018, 2021 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : CPP
--
-- This Module implements a NTriples parser, returning a
-- new 'RDFGraph' consisting of triples and namespace information parsed from
-- the supplied NTriples input string, or an error indication.
--
-- REFERENCES:
--
-- - \"RDF Test Cases\",
-- W3C Recommendation 10 February 2004,
--
--
-- NOTES:
--
-- - If the URI is actually an IRI (Internationalized Resource Identifiers)
-- then the parser will fail since 'Network.URI.parseURI' fails.
--
-- - The case of language tags is retained.
--
-- - Update to the document \"N-Triples. A line-based syntax for an RDF graph\"
-- W3C Working Group Note 09 April 2013,
--
--
--------------------------------------------------------------------------------
module Swish.RDF.Parser.NTriples
( ParseResult
, parseNT
)
where
import Swish.GraphClass (arc)
import Swish.Namespace (ScopedName, makeURIScopedName)
import Swish.RDF.Graph (RDFGraph, RDFLabel(..), addArc, emptyRDFGraph)
import Swish.RDF.Vocabulary (LanguageTag, toLangTag)
import Swish.RDF.Parser.Utils (ParseResult
, runParserWithError
, ignore
, skipMany
, noneOf
, char
, string
, eoln
, fullStop
, hex4
, hex8
)
import Control.Applicative
import Network.URI (parseURI)
import qualified Data.Text as T
import qualified Data.Text.Lazy as L
import Data.Char (isAsciiLower, isAsciiUpper, isDigit, ord)
#if MIN_VERSION_base(4, 7, 0)
import Data.Functor (($>))
#endif
import Data.Maybe (fromMaybe)
import Text.ParserCombinators.Poly.StateText
#if !MIN_VERSION_base(4, 7, 0)
($>) :: Functor f => f a -> b -> f b
($>) = flip (<$)
#endif
----------------------------------------------------------------------
-- Define parser state and helper functions
----------------------------------------------------------------------
-- | NT parser state
data NTState = NTState
{ graphState :: RDFGraph -- Graph under construction
}
emptyState :: NTState
emptyState = NTState { graphState = emptyRDFGraph }
-- Return function to update graph in NT parser state,
-- using the supplied function of a graph. This is for use
-- with stUpdate.
--
updateGraph :: (RDFGraph -> RDFGraph) -> NTState -> NTState
updateGraph f s = s { graphState = f (graphState s) }
----------------------------------------------------------------------
-- Define top-level parser function:
-- accepts a string and returns a graph or error
----------------------------------------------------------------------
-- | Parser that carries around a NTState record.
type NTParser a = Parser NTState a
-- | Parse a string.
--
parseNT ::
L.Text -- ^ input in NTriples format.
-> ParseResult
parseNT = parsefromText ntripleDoc
{-
-- useful for testing
test :: String -> RDFGraph
test = either error id . parseNT
-}
-- | Function to supply initial context and parse supplied term.
--
-- Used for debugging.
parsefromText ::
NTParser a -- ^ parser to apply
-> L.Text -- ^ input to be parsed
-> Either String a
parsefromText parser = runParserWithError parser emptyState
-- helper routines
{-
lineFeed :: NTParser ()
lineFeed = ignore (char '\r')
-}
-- Add statement to graph in NT parser state
addStatement :: RDFLabel -> RDFLabel -> RDFLabel -> NTParser ()
addStatement s p o = stUpdate (updateGraph (addArc (arc s p o) ))
----------------------------------------------------------------------
-- Syntax productions
----------------------------------------------------------------------
{-
EBNF from the specification, using the notation from XML 1.0, second edition,
is included inline below.
We do not force ASCII 7-bit semantics here yet.
space ::= #x20 /* US-ASCII space - decimal 32 */
cr ::= #xD /* US-ASCII carriage return - decimal 13 */
lf ::= #xA /* US-ASCII line feed - decimal 10 */
tab ::= #x9 /* US-ASCII horizontal tab - decimal 9 */
The productions are kept as close as possible to the specification
for now.
-}
{-
ntripleDoc ::= line*
line ::= ws* ( comment | triple )? eoln
We relax the rule that the input must be empty or end with a new line.
ntripleDoc :: NTParser RDFGraph
ntripleDoc = graphState <$> (many line *> eof *> getState)
line :: NTParser ()
line = skipMany ws *> optional (comment <|> triple) *> eoln
-}
ntripleDoc :: NTParser RDFGraph
ntripleDoc = graphState <$> (sepBy line eoln *> optional eoln *> skipWS *> eof *> stGet)
line :: NTParser ()
line = skipWS *> ignore (optional (comment <|> triple))
{-
ws ::= space | tab
Could use whiteSpace rule here, but that would permit
constructs (e.g. comments) where we do not support them.
-}
isWS :: Char -> Bool
isWS = (`elem` (" \t" :: String))
{-
ws :: NTParser ()
-- ws = ignore (char ' ' <|> tab)
ws = ignore $ satisfy isWS
-}
skipWS :: NTParser ()
skipWS = ignore $ manySatisfy isWS
skip1WS :: NTParser ()
skip1WS = ignore $ many1Satisfy isWS
{-
comment ::= '#' ( character - ( cr | lf ) )*
-}
comment :: NTParser ()
comment = char '#' *> skipMany (noneOf "\r\n")
{-
eoln ::= cr | lf | cr lf
-}
{-
name ::= [A-Za-z][A-Za-z0-9]*
-}
isaz, isAZ, is09 :: Char -> Bool
isaz = isAsciiLower
isAZ = isAsciiUpper
is09 = isDigit
isaZ, isaZ09 :: Char -> Bool
isaZ c = isaz c || isAZ c
isaZ09 c = isaZ c || is09 c
isHeadChar, isBodyChar :: Char -> Bool
isHeadChar = isaZ
isBodyChar = isaZ09
name :: NTParser L.Text
name = L.cons <$> satisfy isHeadChar <*> manySatisfy isBodyChar
nameStr :: NTParser String
nameStr = L.unpack <$> name
{-
triple ::= subject ws+ predicate ws+ object ws* '.' ws*
-}
triple :: NTParser ()
triple =
do
s <- subject <* skip1WS
p <- predicate <* skip1WS
o <- object <* (skipWS >> fullStop >> skipWS)
addStatement s p o
{-
subject ::= uriref | nodeID
predicate ::= uriref
object ::= uriref | nodeID | literal
-}
subject :: NTParser RDFLabel
subject = urirefLbl <|> nodeID
predicate :: NTParser RDFLabel
predicate = urirefLbl
object :: NTParser RDFLabel
object = urirefLbl <|> nodeID <|> literal
{-
uriref ::= '<' absoluteURI '>'
absoluteURI ::= character+ with escapes as defined below (from section 'URI References')
The absoluteURI production encodes a Unicode string representing an RDF URI references as specified in
[RDF-CONCEPTS]. These are encoded in N-Triples using the escapes described in section Strings.
-}
uriref :: NTParser ScopedName
uriref = do
ignore $ char '<'
uri <- manyFinally' character (char '>')
maybe (failBad ("Invalid URI: <" ++ uri ++ ">"))
(return . makeURIScopedName)
(parseURI uri)
urirefLbl :: NTParser RDFLabel
urirefLbl = Res <$> uriref
{-
nodeID ::= '_:' name
-}
nodeID :: NTParser RDFLabel
nodeID = Blank <$> (string "_:" *> nameStr)
{-
literal ::= langString | datatypeString
langString ::= '"' string '"' ( '@' language )?
datatypeString ::= '"' string '"' '^^' uriref
language ::= [a-z]+ ('-' [a-z0-9]+ )*
encoding a language tag.
string ::= character* with escapes as defined in section Strings
-}
literal :: NTParser RDFLabel
literal = do
lit <- T.pack <$> ntstring
opt <- optional dtlang
return $ case opt of
Just (Left lcode) -> LangLit lit lcode
Just (Right dtype) -> TypedLit lit dtype
_ -> Lit lit
ntstring :: NTParser String
ntstring = bracket (char '"') (char '"') (many character)
dtlang :: NTParser (Either LanguageTag ScopedName)
dtlang =
(char '@' *> commit (Left <$> language))
<|> (string "^^" *> commit (Right <$> uriref))
-- Note that toLangTag may fail since it does some extra
-- validation not done by the parser (mainly on the length of the
-- primary and secondary tags).
--
-- NOTE: This parser does not accept multiple secondary tags which RFC3066
-- does.
--
-- Although the EBNF only lists [a-z] we also support upper case values,
-- since the W3C Turtle test case includes a NTriples file with
-- "...@en-UK" in it.
--
language :: NTParser LanguageTag
language = do
h <- many1Satisfy isaZ
mt <- optional $ L.cons <$> char '-' <*> many1Satisfy isaZ09
let lbl = L.toStrict $ L.append h $ fromMaybe L.empty mt
case toLangTag lbl of
Just lt -> return lt
_ -> fail ("Invalid language tag: " ++ T.unpack lbl) -- should this be failBad?
{-
String handling:
EBNF has:
character ::= [#x20-#x7E] /* US-ASCII space to decimal 126 */
Additional information from:
http://www.w3.org/TR/rdf-testcases/#ntrip_strings
N-Triples strings are sequences of US-ASCII character productions encoding [UNICODE] character strings. The characters outside the US-ASCII range and some other specific characters are made available by \-escape sequences as follows:
Unicode character
(with code point u) N-Triples encoding
[#x0-#x8] \uHHHH
4 required hexadecimal digits HHHH encoding Unicode character u
#x9 \t
#xA \n
[#xB-#xC] \uHHHH
4 required hexadecimal digits HHHH encoding Unicode character u
#xD \r
[#xE-#x1F] \uHHHH
4 required hexadecimal digits HHHH encoding Unicode character u
[#x20-#x21] the character u
#x22 \"
[#x23-#x5B] the character u
#x5C \\
[#x5D-#x7E] the character u
[#x7F-#xFFFF] \uHHHH
4 required hexadecimal digits HHHH encoding Unicode character u
[#10000-#x10FFFF] \UHHHHHHHH
8 required hexadecimal digits HHHHHHHH encoding Unicode character u
where H is a hexadecimal digit: [#x30-#x39],[#x41-#x46] (0-9, uppercase A-F).
This escaping satisfies the [CHARMOD] section Reference Processing Model on making the full Unicode character range U+0 to U+10FFFF available to applications and providing only one way to escape any character.
-}
-- 0x22 is " and 0x5c is \
isAsciiChar :: Char -> Bool
isAsciiChar c = let i = ord c
in i >= 0x20 && i <= 0x21 ||
i >= 0x23 && i <= 0x5b ||
i >= 0x5d && i <= 0x7e
protectedChar :: NTParser Char
protectedChar =
(char 't' $> '\t')
<|> (char 'n' $> '\n')
<|> (char 'r' $> '\r')
<|> (char '"' $> '"')
<|> (char '\\' $> '\\')
<|> (char 'u' *> hex4)
<|> (char 'U' *> hex8)
character :: NTParser Char
character =
(char '\\' *> protectedChar)
<|> satisfy isAsciiChar
--------------------------------------------------------------------------------
--
-- Copyright (c) 2011, 2012, 2013, 2018 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/RDF/Parser/N3.hs 0000644 0000000 0000000 00000110500 13767237337 015643 0 ustar 00 0000000 0000000 {-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
--------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : N3
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011, 2012, 2013, 2014, 2018, 2020 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : CPP, OverloadedStrings
--
-- This Module implements a Notation 3 parser, returning a
-- new 'RDFGraph' consisting of triples and namespace information parsed from
-- the supplied N3 input string, or an error indication.
--
-- REFERENCES:
--
-- - \"Notation3 (N3): A readable RDF syntax\",
-- W3C Team Submission 14 January 2008,
--
--
-- - Tim Berners-Lee's design issues series notes and description,
--
--
-- - Notation 3 Primer by Sean Palmer,
--
--
-- NOTES:
--
-- - The parser needs to be updated to the latest version
-- (\"W3C Team Submission 28 March 2011\",
-- )
--
-- - UTF-8 handling is not really tested.
--
-- - No performance testing has been applied.
--
-- - Not all N3 grammar elements are supported, including:
--
-- - @\@forSome@ (we read it in but ignore the arguments)
--
-- - @\@forAll@ (this causes a parse error)
--
-- - formulae are lightly tested
--
-- - string support is incomplete (e.g. unrecognized escape characters
-- such as @\\q@ are probably handled incorrectly)
--
--------------------------------------------------------------------------------
module Swish.RDF.Parser.N3
( ParseResult
, parseN3
, parseN3fromText
, parseAnyfromText
, parseTextFromText, parseAltFromText
, parseNameFromText -- , parsePrefixFromText
, parseAbsURIrefFromText, parseLexURIrefFromText, parseURIref2FromText
-- * Exports for parsers that embed Notation3 in a bigger syntax
, N3Parser, N3State(..), SpecialMap
, getPrefix -- a combination of the old defaultPrefix and namedPrefix productions
, n3symbol -- replacement for uriRef2 -- TODO: check this is semantically correct
, quickVariable -- was varid
, lexUriRef
, document, subgraph
, newBlankNode
)
where
import Swish.GraphClass (arc)
import Swish.Namespace
( Namespace
, ScopedName
, makeNamespace
, getNamespaceTuple
, getScopeNamespace
, getScopedNameURI
, getScopeNamespace
, makeURIScopedName
, makeQNameScopedName
, makeNSScopedName
, nullScopedName
)
import Swish.QName (QName, newLName)
import Swish.RDF.Graph
( RDFGraph, RDFLabel(..)
, ToRDFLabel(..)
, NamespaceMap
, LookupFormula(..)
, addArc
, setFormula
, setNamespaces
, emptyRDFGraph
)
import Swish.RDF.Datatype (makeDatatypedLiteral)
import Swish.RDF.Vocabulary
( LanguageTag
, toLangTag
, rdfType
, rdfFirst, rdfRest, rdfNil
, owlSameAs, logImplies
, xsdBoolean, xsdInteger, xsdDecimal, xsdDouble
)
import Swish.RDF.Parser.Utils
( SpecialMap
, ParseResult
, runParserWithError
-- , mapPrefix
, prefixTable
, specialTable
, ignore
, notFollowedBy
, endBy
, sepEndBy
-- , manyTill
, noneOf
, char
, ichar
, string
, stringT
, symbol
, lexeme
, whiteSpace
, hex4
, hex8
, appendURIs
)
import Control.Applicative
import Control.Monad (forM_, foldM)
import Data.Char (isSpace, isDigit, ord, isAsciiLower)
#if MIN_VERSION_base(4, 7, 0)
import Data.Functor (($>))
#endif
import Data.Maybe (fromMaybe, fromJust)
import Data.Word (Word32)
import Network.URI (URI(..), parseURIReference)
import Text.ParserCombinators.Poly.StateText
import qualified Data.Map as M
import qualified Data.Text as T
import qualified Data.Text.Lazy as L
#if !MIN_VERSION_base(4, 7, 0)
($>) :: Functor f => f a -> b -> f b
($>) = flip (<$)
#endif
----------------------------------------------------------------------
-- Define parser state and helper functions
----------------------------------------------------------------------
-- | N3 parser state
data N3State = N3State
{ graphState :: RDFGraph -- Graph under construction
, thisNode :: RDFLabel -- current context node (aka 'this')
, prefixUris :: NamespaceMap -- namespace prefix mapping table
, syntaxUris :: SpecialMap -- special name mapping table
, nodeGen :: Word32 -- blank node id generator
, keywordsList :: [T.Text] -- contents of the @keywords statement
, allowLocalNames :: Bool -- True if @keywords used so that bare names are QNames in default namespace
}
-- | Functions to update N3State vector (use with stUpdate)
setPrefix :: Maybe T.Text -> URI -> N3State -> N3State
setPrefix pre uri st = st { prefixUris=p' }
where
p' = M.insert pre uri (prefixUris st)
-- | Set name for special syntax element
setSName :: String -> ScopedName -> N3State -> N3State
setSName nam snam st = st { syntaxUris=s' }
where
s' = M.insert nam snam (syntaxUris st)
setSUri :: String -> URI -> N3State -> N3State
setSUri nam = setSName nam . makeURIScopedName
-- | Set the list of tokens that can be used without needing the leading
-- \@ symbol.
setKeywordsList :: [T.Text] -> N3State -> N3State
setKeywordsList ks st = st { keywordsList = ks, allowLocalNames = True }
-- Functions to access state:
-- | Get name for special syntax element, default null
getSName :: N3State -> String -> ScopedName
getSName st nam = M.findWithDefault nullScopedName nam $ syntaxUris st
getSUri :: N3State -> String -> URI
getSUri st nam = getScopedNameURI $ getSName st nam
-- Map prefix to URI
getPrefixURI :: N3State -> Maybe T.Text -> Maybe URI
getPrefixURI st pre = M.lookup pre (prefixUris st)
getKeywordsList :: N3State -> [T.Text]
getKeywordsList = keywordsList
getAllowLocalNames :: N3State -> Bool
getAllowLocalNames = allowLocalNames
-- Return function to update graph in N3 parser state,
-- using the supplied function of a graph
--
updateGraph :: (RDFGraph -> RDFGraph) -> N3State -> N3State
updateGraph f s = s { graphState = f (graphState s) }
----------------------------------------------------------------------
-- Define top-level parser function:
-- accepts a string and returns a graph or error
----------------------------------------------------------------------
-- | The N3 parser.
type N3Parser a = Parser N3State a
-- | Parse a string as N3 (with no real base URI).
--
-- See 'parseN3' if you need to provide a base URI.
--
parseN3fromText ::
L.Text -- ^ input in N3 format.
-> ParseResult
parseN3fromText = flip parseN3 Nothing
-- | Parse a string with an optional base URI.
--
-- See also 'parseN3fromString'.
--
parseN3 ::
L.Text -- ^ input in N3 format.
-> Maybe QName -- ^ optional base URI
-> ParseResult
parseN3 txt mbase = parseAnyfromText document mbase txt
{-
-- useful for testing
test :: String -> RDFGraph
test = either error id . parseAnyfromString document Nothing
-}
hashURI :: URI
hashURI = fromJust $ parseURIReference "#"
emptyState ::
Maybe QName -- ^ starting base for the graph
-> N3State
emptyState mbase =
let pmap = M.singleton Nothing hashURI
muri = fmap (makeQNameScopedName Nothing) mbase
smap = M.fromList $ specialTable muri
in N3State
{ graphState = emptyRDFGraph
, thisNode = NoNode
, prefixUris = pmap
, syntaxUris = smap
, nodeGen = 0
, keywordsList = ["a", "is", "of", "true", "false"] -- not 100% sure about true/false here
, allowLocalNames = False
}
-- TODO: change from QName to URI for the base?
-- | Function to supply initial context and parse supplied term.
--
parseAnyfromText :: N3Parser a -- ^ parser to apply
-> Maybe QName -- ^ base URI of the input, or @Nothing@ to use default base value
-> L.Text -- ^ input to be parsed
-> Either String a
parseAnyfromText parser mbase = runParserWithError parser (emptyState mbase)
-- | Create a new blank node.
newBlankNode :: N3Parser RDFLabel
newBlankNode = do
n <- stQuery (succ . nodeGen)
stUpdate $ \s -> s { nodeGen = n }
return $ Blank (show n)
-- Test functions for selected element parsing
-- TODO: remove these
-- | Used in testing.
parseTextFromText :: String -> L.Text -> Either String String
parseTextFromText s =
parseAnyfromText (string s) Nothing
-- | Used in testing.
parseAltFromText :: String -> String -> L.Text -> Either String String
parseAltFromText s1 s2 =
parseAnyfromText (string s1 <|> string s2) Nothing
-- | Used in testing.
parseNameFromText :: L.Text -> Either String String
parseNameFromText =
parseAnyfromText n3NameStr Nothing
{-
This has been made tricky by the attempt to remove the default list
of prefixes from the starting point of a N3 parse and the subsequent
attempt to add every new namespace we come across to the parser state.
So we add in the original default namespaces for testing, since
this routine is really for testing.
-}
addTestPrefixes :: N3Parser ()
addTestPrefixes = stUpdate $ \st -> st { prefixUris =
M.fromList
$ map getNamespaceTuple prefixTable
} -- should append to existing map
{-
parsePrefixFromText :: L.Text -> Either String URI
parsePrefixFromText =
parseAnyfromText p Nothing
where
p = do
addTestPrefixes
pref <- n3Name
st <- stGet
case getPrefixURI st (Just pref) of
Just uri -> return uri
_ -> fail $ "Undefined prefix: '" ++ pref ++ "'"
-}
-- | Used in testing.
parseAbsURIrefFromText :: L.Text -> Either String URI
parseAbsURIrefFromText =
parseAnyfromText explicitURI Nothing
-- | Used in testing.
parseLexURIrefFromText :: L.Text -> Either String URI
parseLexURIrefFromText =
parseAnyfromText lexUriRef Nothing
-- | Used in testing.
parseURIref2FromText :: L.Text -> Either String ScopedName
parseURIref2FromText =
parseAnyfromText (addTestPrefixes *> n3symbol) Nothing
----------------------------------------------------------------------
-- Syntax productions
----------------------------------------------------------------------
-- helper routines
comma, semiColon , fullStop :: N3Parser ()
comma = ignore $ symbol ","
semiColon = ignore $ symbol ";"
fullStop = ignore $ symbol "."
-- a specialization of bracket/between
br :: String -> String -> N3Parser a -> N3Parser a
br lsym rsym = bracket (symbol lsym) (symbol rsym)
-- to make porting from parsec to polyparse easier
between :: Parser s lbr -> Parser s rbr -> Parser s a -> Parser s a
between = bracket
-- The @ character is optional if the keyword is in the
-- keyword list
--
atSign :: T.Text -> N3Parser ()
atSign s = do
st <- stGet
let p = ichar '@'
if s `elem` getKeywordsList st
then ignore $ optional p
else p
atWord :: T.Text -> N3Parser T.Text
atWord s = do
atSign s
-- TODO: does it really make sense to add the not-followed-by-a-colon rule here?
-- apply to both cases even though should only really be necessary
-- when the at sign is not given
--
lexeme $ stringT s *> notFollowedBy (== ':')
return s
{-
Since operatorLabel can be used to add a label with an
unknown namespace, we need to ensure that the namespace
is added if not known. If the namespace prefix is already
in use then it is over-written (rather than add a new
prefix for the label).
TODO:
- could we use the reverse lookupmap functionality to
find if the given namespace URI is in the namespace
list? If it is, use it's key otherwise do a
mapReplace for the input namespace.
-}
operatorLabel :: ScopedName -> N3Parser RDFLabel
operatorLabel snam = do
st <- stGet
let (pkey, pval) = getNamespaceTuple $ getScopeNamespace snam
opmap = prefixUris st
rval = Res snam
-- TODO: the lookup and the replacement could be fused
case M.lookup pkey opmap of
Just val | val == pval -> return rval
| otherwise -> do
stUpdate $ \s -> s { prefixUris = M.insert pkey pval opmap }
return rval
_ -> do
stUpdate $ \s -> s { prefixUris = M.insert pkey pval opmap }
return rval
{-
Add statement to graph in N3 parser state.
To support literals that are written directly/implicitly - i.e. as
true/false/1/1.0/1.0e23 - rather than a string with an explicit
datatype we need to special case handling of the object label for
literals. Is this actually needed? The N3 Formatter now doesn't
display the xsd: datatypes on output, but there may be issues with
other formats (e.g RDF/XML once it is supported).
-}
type AddStatement = RDFLabel -> N3Parser ()
addStatement :: RDFLabel -> RDFLabel -> AddStatement
addStatement s p o@(TypedLit _ dtype) | dtype `elem` [xsdBoolean, xsdInteger, xsdDecimal, xsdDouble] = do
ost <- stGet
let stmt = arc s p o
oldp = prefixUris ost
ogs = graphState ost
(ns, uri) = getNamespaceTuple $ getScopeNamespace dtype
newp = M.insert ns uri oldp
stUpdate $ \st -> st { prefixUris = newp, graphState = addArc stmt ogs }
addStatement s p o = stUpdate (updateGraph (addArc (arc s p o) ))
addStatementRev :: RDFLabel -> RDFLabel -> AddStatement
addStatementRev o p s = addStatement s p o
{-
A number of productions require a name, which starts with
[A-Z_a-z#x00c0-#x00d6#x00d8-#x00f6#x00f8-#x02ff#x0370-#x037d#x037f-#x1fff#x200c-#x200d#x2070-#x218f#x2c00-#x2fef#x3001-#xd7ff#xf900-#xfdcf#xfdf0-#xfffd#x00010000-#x000effff]
and then has
[\-0-9A-Z_a-z#x00b7#x00c0-#x00d6#x00d8-#x00f6#x00f8-#x037d#x037f-#x1fff#x200c-#x200d#x203f-#x2040#x2070-#x218f#x2c00-#x2fef#x3001-#xd7ff#xf900-#xfdcf#xfdf0-#xfffd#x00010000-#x000effff]*
we encode this as the n3Name production
-}
isaz, is09, isaz09 :: Char -> Bool
isaz = isAsciiLower
is09 = isDigit
isaz09 c = isaz c || is09 c
match :: (Ord a) => a -> [(a,a)] -> Bool
match v = any (\(l,h) -> v >= l && v <= h)
startChar :: Char -> Bool
startChar c = let i = ord c
in c == '_' ||
match c [('A', 'Z'), ('a', 'z')] ||
match i [(0x00c0, 0x00d6), (0x00d8, 0x00f6), (0x00f8, 0x02ff),
(0x0370, 0x037d),
(0x037f, 0x1fff), (0x200c, 0x200d),
(0x2070, 0x218f), (0x2c00, 0x2fef), (0x3001, 0xd7ff),
(0xf900, 0xfdcf), (0xfdf0, 0xfffd),
(0x00010000, 0x000effff)]
inBody :: Char -> Bool
inBody c = let i = ord c
in c `elem` ("-_"::String) || i == 0x007 ||
match c [('0', '9'), ('A', 'Z'), ('a', 'z')] ||
match i [(0x00c0, 0x00d6), (0x00d8, 0x00f6), (0x00f8, 0x037d),
(0x037f, 0x1fff), (0x200c, 0x200d), (0x203f, 0x2040),
(0x2070, 0x218f), (0x2c00, 0x2fef), (0x3001, 0xd7ff),
(0xf900, 0xfdcf), (0xfdf0, 0xfffd),
(0x00010000, 0x000effff)]
-- should this be strict or lazy text?
n3Name :: N3Parser T.Text
n3Name = T.cons <$> n3Init <*> n3Body
where
n3Init = satisfy startChar
n3Body = L.toStrict <$> manySatisfy inBody
n3NameStr :: N3Parser String
n3NameStr = T.unpack <$> n3Name
{-
quickvariable ::= \?[A-Z_a-z#x00c0-#x00d6#x00d8-#x00f6#x00f8-#x02ff#x0370-#x037d#x037f-#x1fff#x200c-#x200d#x2070-#x218f#x2c00-#x2fef#x3001-#xd7ff#xf900-#xfdcf#xfdf0-#xfffd#x00010000-#x000effff][\-0-9A-Z_a-z#x00b7#x00c0-#x00d6#x00d8-#x00f6#x00f8-#x037d#x037f-#x1fff#x200c-#x200d#x203f-#x2040#x2070-#x218f#x2c00-#x2fef#x3001-#xd7ff#xf900-#xfdcf#xfdf0-#xfffd#x00010000-#x000effff]*
-}
-- TODO: is mapping to Var correct?
-- | Match @?@.
quickVariable :: N3Parser RDFLabel
quickVariable = char '?' *> (Var <$> n3NameStr)
{-
string ::= ("""[^"\\]*(?:(?:\\.|"(?!""))[^"\\]*)*""")|("[^"\\]*(?:\\.[^"\\]*)*")
or
string ::= tripleQuoted | singleQUoted
-}
n3string :: N3Parser T.Text
n3string = tripleQuoted <|> singleQuoted
{-
singleQuoted ::= "[^"\\]*(?:\\.[^"\\]*)*"
asciiChars :: String
asciiChars = map chr [0x20..0x7e]
asciiCharsN3 :: String
asciiCharsN3 = filter (`notElem` "\\\"") asciiChars
-}
digit :: N3Parser Char
digit = satisfy isDigit
{-
This is very similar to NTriples accept that also allow the escaping of '
even though it is not required.
The Python rules allow \N{name}, where name is the Unicode name. It's
not clear whether we need to support this too, so for now we do not.
-}
protectedChar :: N3Parser Char
protectedChar =
(char 't' $> '\t')
<|> (char 'n' $> '\n')
<|> (char 'r' $> '\r')
<|> (char '"' $> '"')
<|> (char '\'' $> '\'')
<|> (char '\\' $> '\\')
<|> (char 'u' *> hex4)
<|> (char 'U' *> hex8)
-- Accept an escape character or any character as long as it isn't
-- a new-line or quote. Unrecognized escape sequences should therefore
-- be left alone by this.
--
n3Character :: N3Parser Char
n3Character =
(char '\\' *> (protectedChar <|> return '\\'))
<|> noneOf "\"\n"
{-
<|> (oneOf asciiCharsN3 > "ASCII character")
-- TODO: bodyChar and asciiCharsN3 overlap
<|> (oneOf bodyChar > "Unicode character")
-}
sQuot :: N3Parser Char
sQuot = char '"'
{-
TODO: there must be a better way of building up the Text
-}
singleQuoted :: N3Parser T.Text
singleQuoted = T.pack <$> bracket sQuot sQuot (many n3Character)
{-
tripleQUoted ::= """[^"\\]*(?:(?:\\.|"(?!""))[^"\\]*)*"""
The following may not match the output format we now create (with the
move to the Turtle Candidate Recommendation), so re-writing as a test,
but this means pulling in a lot of Turtle productions, which should
be shared.
tripleQuoted = tQuot *> fmap T.pack (manyTill (n3Character <|> sQuot <|> char '\n') tQuot)
where
-- tQuot = try (count 3 sQuot)
tQuot = exactly 3 sQuot
-}
tripleQuoted :: N3Parser T.Text
tripleQuoted =
let sep = exactly 3 sQuot
in T.concat <$> bracket sep sep (many _tCharsLong)
{-- Turtle productions: start --}
oneOrTwo :: N3Parser T.Text
oneOrTwo = do
ignore $ char '"'
mb <- optional (char '"')
case mb of
Just _ -> return "\"\""
_ -> return "\""
_multiQuote :: N3Parser T.Text
_multiQuote = do
mq <- optional oneOrTwo
r <- noneOf "\"\\"
return $ fromMaybe T.empty mq `T.snoc` r
_tCharsLong :: N3Parser T.Text
_tCharsLong =
T.singleton <$> _protChar
<|> _multiQuote
_protChar :: N3Parser Char
_protChar = char '\\' *> (_echar' <|> _uchar')
_echar' :: N3Parser Char
_echar' =
(char 't' $> '\t') <|>
(char 'b' $> '\b') <|>
(char 'n' $> '\n') <|>
(char 'r' $> '\r') <|>
(char 'f' $> '\f') <|>
(char '\\' $> '\\') <|>
(char '"' $> '"') <|>
(char '\'' $> '\'')
_uchar' :: N3Parser Char
_uchar' =
(char 'u' *> commit hex4)
<|>
(char 'U' *> commit hex8)
{-- Turtle productions: end --}
getDefaultPrefix :: N3Parser Namespace
getDefaultPrefix = do
s <- stGet
case getPrefixURI s Nothing of
Just uri -> return $ makeNamespace Nothing uri
_ -> fail "No default prefix defined; how unexpected!"
addBase :: URI -> N3Parser ()
addBase = stUpdate . setSUri "base"
addPrefix :: Maybe T.Text -> URI -> N3Parser ()
addPrefix p = stUpdate . setPrefix p
{-|
Update the set of keywords that can be given without
an \@ sign.
-}
updateKeywordsList :: [T.Text] -> N3Parser ()
updateKeywordsList = stUpdate . setKeywordsList
{-
document ::= | statements_optional EOF
-}
-- | Process a N3 document, returning a graph.
document :: N3Parser RDFGraph
document = mkGr <$> (whiteSpace *> statementsOptional *> eof *> stGet)
where
mkGr s = setNamespaces (prefixUris s) (graphState s)
{-
statements_optional ::= | statement "." statements_optional
| void
-}
statementsOptional :: N3Parser ()
statementsOptional = ignore $ endBy (lexeme statement) fullStop
{-
statement ::= | declaration
| existential
| simpleStatement
| universal
-}
statement :: N3Parser ()
statement =
declaration
<|> existential
<|> universal
<|> simpleStatement
-- having an error here leads to less informative errors in general, it seems
-- > "statement (existential or universal quantification or a simple statement)"
{-
declaration ::= | "@base" explicituri
| "@keywords" barename_csl
| "@prefix" prefix explicituri
-}
-- TODO: do we need the try statements here? atWord would need to have a try on '@'
-- (if applicable) which should mean being able to get rid of try
--
declaration :: N3Parser ()
declaration = oneOf [
atWord "base" >> explicitURI >>= addBase,
atWord "keywords" >> bareNameCsl >>= updateKeywordsList,
atWord "prefix" *> getPrefix
]
{-
(try (atWord "base") >> explicitURI >>= addBase)
<|>
(try (atWord "keywords") >> bareNameCsl >>= updateKeywordsList)
<|>
(try (atWord "prefix") *> getPrefix)
-}
-- | Process the remainder of an @\@prefix@ line (after this
-- has been processed). The prefix value and URI are added to the parser
-- state.
getPrefix :: N3Parser ()
getPrefix = do
p <- lexeme prefix
u <- explicitURI
addPrefix p u
{-
explicituri ::= <[^>]*>
Note: white space is to be ignored within <>
-}
explicitURI :: N3Parser URI
explicitURI = do
ignore $ char '<'
ustr <- manyFinally' ((satisfy isSpace *> next) <|> next) (char '>')
case parseURIReference ustr of
Nothing -> failBad $ "Invalid URI: <" ++ ustr ++ ">"
Just uref -> do
s <- stGet
let base = getSUri s "base"
either fail return $ appendURIs base uref
-- production from the old parser; used in SwishScript
-- | An explicitly given URI followed by white space.
lexUriRef :: N3Parser URI
lexUriRef = lexeme explicitURI
{-
barename ::= [A-Z_a-z#x00c0-#x00d6#x00d8-#x00f6#x00f8-#x02ff#x0370-#x037d#x037f-#x1fff#x200c-#x200d#x2070-#x218f#x2c00-#x2fef#x3001-#xd7ff#xf900-#xfdcf#xfdf0-#xfffd#x00010000-#x000effff][\-0-9A-Z_a-z#x00b7#x00c0-#x00d6#x00d8-#x00f6#x00f8-#x037d#x037f-#x1fff#x200c-#x200d#x203f-#x2040#x2070-#x218f#x2c00-#x2fef#x3001-#xd7ff#xf900-#xfdcf#xfdf0-#xfffd#x00010000-#x000effff]*
barename_csl ::= | barename barename_csl_tail
| void
barename_csl_tail ::= | "," barename barename_csl_tail
| void
-}
bareNameCsl :: N3Parser [T.Text]
bareNameCsl = sepBy (lexeme bareName) comma
bareName :: N3Parser T.Text
bareName = n3Name
{-
prefix ::= ([A-Z_a-z#x00c0-#x00d6#x00d8-#x00f6#x00f8-#x02ff#x0370-#x037d#x037f-#x1fff#x200c-#x200d#x2070-#x218f#x2c00-#x2fef#x3001-#xd7ff#xf900-#xfdcf#xfdf0-#xfffd#x00010000-#x000effff][\-0-9A-Z_a-z#x00b7#x00c0-#x00d6#x00d8-#x00f6#x00f8-#x037d#x037f-#x1fff#x200c-#x200d#x203f-#x2040#x2070-#x218f#x2c00-#x2fef#x3001-#xd7ff#xf900-#xfdcf#xfdf0-#xfffd#x00010000-#x000effff]*)?:
-}
prefix :: N3Parser (Maybe T.Text)
prefix = optional (lexeme n3Name) <* char ':'
{-
symbol ::= | explicituri
| qname
symbol_csl ::= | symbol symbol_csl_tail
| void
symbol_csl_tail ::= | "," symbol symbol_csl_tail
| void
-}
-- | Match a N3 symbol (an explicit URI or a QName)
-- and convert it to a 'ScopedName'.
n3symbol :: N3Parser ScopedName
n3symbol =
(makeURIScopedName <$> explicitURI)
<|> qname
symbolCsl :: N3Parser [ScopedName]
symbolCsl = sepBy (lexeme n3symbol) comma
{-
qname ::= (([A-Z_a-z#x00c0-#x00d6#x00d8-#x00f6#x00f8-#x02ff#x0370-#x037d#x037f-#x1fff#x200c-#x200d#x2070-#x218f#x2c00-#x2fef#x3001-#xd7ff#xf900-#xfdcf#xfdf0-#xfffd#x00010000-#x000effff][\-0-9A-Z_a-z#x00b7#x00c0-#x00d6#x00d8-#x00f6#x00f8-#x037d#x037f-#x1fff#x200c-#x200d#x203f-#x2040#x2070-#x218f#x2c00-#x2fef#x3001-#xd7ff#xf900-#xfdcf#xfdf0-#xfffd#x00010000-#x000effff]*)?:)?[A-Z_a-z#x00c0-#x00d6#x00d8-#x00f6#x00f8-#x02ff#x0370-#x037d#x037f-#x1fff#x200c-#x200d#x2070-#x218f#x2c00-#x2fef#x3001-#xd7ff#xf900-#xfdcf#xfdf0-#xfffd#x00010000-#x000effff][\-0-9A-Z_a-z#x00b7#x00c0-#x00d6#x00d8-#x00f6#x00f8-#x037d#x037f-#x1fff#x200c-#x200d#x203f-#x2040#x2070-#x218f#x2c00-#x2fef#x3001-#xd7ff#xf900-#xfdcf#xfdf0-#xfffd#x00010000-#x000effff]*
TODO:
Note that, for now, we explicitly handle blank nodes
(of the form _:name) direcly in pathItem'.
This is not a good idea since qname' is used elsewhere
and so shouldn't we do the same thing there too?
-}
-- for now assume that the parsing rule for the local part
-- will not create an invalid LName.
toName :: Namespace -> T.Text -> ScopedName
toName ns l =
case newLName l of
Just local -> makeNSScopedName ns local
_ -> error $ "Invalid local name: " ++ T.unpack l
qname :: N3Parser ScopedName
qname = qname1 <|> qname2
qname1 :: N3Parser ScopedName
qname1 = fmap (uncurry toName) (char ':' >> g)
where
g = (,) <$> getDefaultPrefix <*> (n3Name <|> return "")
qname2 :: N3Parser ScopedName
qname2 = n3Name >>= fullOrLocalQName
fullOrLocalQName :: T.Text -> N3Parser ScopedName
fullOrLocalQName name =
(char ':' *> fullQName name)
<|> localQName name
fullQName :: T.Text -> N3Parser ScopedName
fullQName name = toName <$> findPrefix name <*> (n3Name <|> pure "")
findPrefix :: T.Text -> N3Parser Namespace
findPrefix pre = do
st <- stGet
case M.lookup (Just pre) (prefixUris st) of
Just uri -> return $ makeNamespace (Just pre) uri
Nothing -> failBad $ "Prefix '" ++ T.unpack pre ++ ":' not bound."
localQName :: T.Text -> N3Parser ScopedName
localQName name = do
st <- stGet
if getAllowLocalNames st
then let g = (,) <$> getDefaultPrefix <*> pure name
in uncurry toName <$> g
else fail ("Invalid 'bare' word: " ++ T.unpack name)-- TODO: not ideal error message; can we handle this case differently?
{-
existential ::= | "@forSome" symbol_csl
For now we just read in the symbols and ignore them,
since we do not mark blank nodes as existentially quantified
(we assume this is the case).
TODO: fix this?
-}
existential :: N3Parser ()
-- existential = try (atWord "forSome") *> symbolCsl >> return ()
existential = (atWord "forSome" *> symbolCsl) $> ()
{-
simpleStatement ::= | subject propertylist
-}
simpleStatement :: N3Parser ()
simpleStatement = subject >>= propertyListWith
{-
subject ::= | expression
-}
subject :: N3Parser RDFLabel
subject = lexeme expression
{-
expression ::= | pathitem pathtail
pathtail ::= | "!" expression
| "^" expression
| void
-}
expression :: N3Parser RDFLabel
expression = do
i <- pathItem
let backwardExpr = char '!' $> addStatementRev
forwardExpr = char '^' $> addStatement
mpt <- optional
( (,) <$> lexeme (forwardExpr <|> backwardExpr) <*> lexeme expression )
case mpt of
Nothing -> return i
Just (addFunc, pt) -> do
bNode <- newBlankNode
addFunc bNode pt i
return bNode
{-
pathitem ::= | "(" pathlist ")"
| "[" propertylist "]"
| "{" formulacontent "}"
| boolean
| literal
| numericliteral
| quickvariable
| symbol
pathlist ::= | expression pathlist
| void
Need to think about how to handle formulae, since need to know the context
of the call to know where to add them.
TOOD: may include direct support for blank nodes here,
namely convert _:stringval -> Blank stringval since although
this should be done by symbol the types don't seem to easily match
up (at first blush anyway)
-}
pathItem :: N3Parser RDFLabel
pathItem =
br "(" ")" pathList
<|> br "[" "]" propertyListBNode
<|> br "{" "}" formulaContent
-- <|> try boolean
<|> boolean
<|> literal
<|> numericLiteral
<|> quickVariable
<|> Blank <$> (string "_:" *> n3NameStr) -- TODO a hack that needs fixing
<|> Res <$> n3symbol
{-
we create a blank node for the list and return it, whilst
adding the list contents to the graph
-}
pathList :: N3Parser RDFLabel
pathList = do
cts <- many (lexeme expression)
eNode <- operatorLabel rdfNil
case cts of
[] -> return eNode
(c:cs) -> do
sNode <- newBlankNode
first <- operatorLabel rdfFirst
addStatement sNode first c
lNode <- foldM addElem sNode cs
rest <- operatorLabel rdfRest
addStatement lNode rest eNode
return sNode
where
addElem prevNode curElem = do
bNode <- newBlankNode
first <- operatorLabel rdfFirst
rest <- operatorLabel rdfRest
addStatement prevNode rest bNode
addStatement bNode first curElem
return bNode
{-
formulacontent ::= | statementlist
statementlist ::= | statement statementtail
| void
statementtail ::= | "." statementlist
| void
-}
restoreState :: N3State -> N3Parser N3State
restoreState origState = do
oldState <- stGet
stUpdate $ const origState { nodeGen = nodeGen oldState }
return oldState
{-
We create a subgraph and assign it to a blank node, returning the
blank node. At present it is a combination of the subgraph and formula
productions from the origial parser.
TODO: is it correct?
-}
formulaContent :: N3Parser RDFLabel
formulaContent = do
bNode <- newBlankNode
pstate <- stGet
stUpdate $ \st -> st { graphState = emptyRDFGraph, thisNode = bNode }
statementList
oldState <- restoreState pstate
stUpdate $ updateGraph $ setFormula (Formula bNode (graphState oldState))
return bNode
-- | Process a sub graph and assign it to the given label.
subgraph :: RDFLabel -> N3Parser RDFGraph
subgraph this = do
pstate <- stGet
stUpdate $ \st -> st { graphState = emptyRDFGraph, thisNode = this }
statementsOptional -- parse statements of formula
oldState <- restoreState pstate
return $ graphState oldState
statementList :: N3Parser ()
statementList = ignore $ sepEndBy (lexeme statement) fullStop
{-
boolean ::= | "@false"
| "@true"
-}
boolean :: N3Parser RDFLabel
boolean = makeDatatypedLiteral xsdBoolean <$>
(atWord "false" <|> atWord "true")
-- (try (atWord "false") <|> atWord "true")
{-
dtlang ::= | "@" langcode
| "^^" symbol
| void
literal ::= | string dtlang
langcode ::= [a-z]+(-[a-z0-9]+)*
-}
literal :: N3Parser RDFLabel
literal = do
lit <- n3string
opt <- optional dtlang
return $ case opt of
Just (Left lcode) -> LangLit lit lcode
Just (Right dtype) -> TypedLit lit dtype
_ -> Lit lit
dtlang :: N3Parser (Either LanguageTag ScopedName)
dtlang =
(char '@' *> (Left <$> langcode))
<|> string "^^" *> (Right <$> n3symbol)
-- Note that toLangTag may fail since it does some extra
-- validation not done by the parser (mainly on the length of the
-- primary and secondary tags).
--
-- NOTE: This parser does not accept multiple secondary tags which RFC3066
-- does.
--
langcode :: N3Parser LanguageTag
langcode = do
h <- many1Satisfy isaz
mt <- optional (L.cons <$> char '-' <*> many1Satisfy isaz09)
let lbl = L.toStrict $ L.append h $ fromMaybe L.empty mt
case toLangTag lbl of
Just lt -> return lt
_ -> fail ("Invalid language tag: " ++ T.unpack lbl) -- should this be failBad?
{-
decimal ::= [-+]?[0-9]+(\.[0-9]+)?
double ::= [-+]?[0-9]+(\.[0-9]+)?([eE][-+]?[0-9]+)
integer ::= [-+]?[0-9]+
numericliteral ::= | decimal
| double
| integer
We actually support 1. for decimal values which isn't supported
by the above production.
TODO: we could convert via something like
maybeRead value :: Double >>= Just . toRDFLabel
which would mean we store the canonical XSD value in the
label, but it is not useful for the xsd:decimal case
since we currently don't have a Haskell type that
goes with it.
-}
numericLiteral :: N3Parser RDFLabel
numericLiteral =
-- -- try (makeDatatypedLiteral xsdDouble <$> n3double)
-- try (d2s <$> n3double)
-- <|> try (makeDatatypedLiteral xsdDecimal <$> n3decimal)
d2s <$> n3double
<|> makeDatatypedLiteral xsdDecimal . T.pack <$> n3decimal
<|> makeDatatypedLiteral xsdInteger . T.pack <$> n3integer
n3sign :: N3Parser Char
n3sign = char '+' <|> char '-'
n3integer :: N3Parser String
n3integer = do
ms <- optional n3sign
ds <- many1 digit
case ms of
Just s -> return $ s : ds
_ -> return ds
n3decimal :: N3Parser String
-- n3decimal = (++) <$> n3integer <*> ( (:) <$> char '.' <*> many1 digit )
n3decimal = (++) <$> n3integer <*> ( (:) <$> char '.' <*> many digit )
n3double :: N3Parser String
n3double = (++)
<$> n3decimal
<*> ( (:) <$> satisfy (`elem` ("eE"::String)) <*> n3integer )
-- Convert a double, as returned by n3double, into it's
-- canonical XSD form. We assume that n3double returns
-- a syntactivally valid Double, so do not bother with reads here
--
d2s :: String -> RDFLabel
d2s s = toRDFLabel (read s :: Double)
{-
propertylist ::= | verb object objecttail propertylisttail
| void
propertylisttail ::= | ";" propertylist
| void
-}
-- it's probably important that bNode is created *after*
-- processing the plist (mainly for the assumptions made by
-- formatting the output as N3; e.g. list/sequence ordering)
--
propertyListBNode :: N3Parser RDFLabel
propertyListBNode = do
plist <- sepEndBy ((,) <$> lexeme verb <*> objectList) semiColon
bNode <- newBlankNode
let addList ((addFunc,vrb),items) = mapM_ (addFunc bNode vrb) items
forM_ plist addList
return bNode
propertyListWith :: RDFLabel -> N3Parser ()
propertyListWith subj =
let -- term = lexeme verb >>= objectListWith subj
term = lexeme verb >>= \(addFunc, vrb) -> objectListWith (addFunc subj vrb)
in ignore $ sepEndBy term semiColon
{-
object ::= | expression
objecttail ::= | "," object objecttail
| void
We change the production rule from objecttail to objectlist for lists of
objects (may change back).
-}
object :: N3Parser RDFLabel
object = lexeme expression
objectList :: N3Parser [RDFLabel]
objectList = sepBy1 object comma
objectWith :: AddStatement -> N3Parser ()
objectWith addFunc = object >>= addFunc
objectListWith :: AddStatement -> N3Parser ()
objectListWith addFunc =
ignore $ sepBy1 (objectWith addFunc) comma
{-
objectList1 :: N3Parser [RDFLabel]
objectList1 = sepBy1 object comma
-}
{-
verb ::= | "<="
| "="
| "=>"
| "@a"
| "@has" expression
| "@is" expression "@of"
| expression
-}
verb :: N3Parser (RDFLabel -> RDFLabel -> AddStatement, RDFLabel)
verb =
-- we check reverse first so that <= is tried before looking for a URI via expression rule
(,) addStatementRev <$> verbReverse
<|> (,) addStatement <$> verbForward
-- those verbs for which subject is on the right and object on the left
verbReverse :: N3Parser RDFLabel
verbReverse =
string "<=" *> operatorLabel logImplies
<|> between (atWord "is") (atWord "of") (lexeme expression)
{-
try (string "<=") *> operatorLabel logImplies
<|> between (try (atWord "is")) (atWord "of") (lexeme expression)
-}
-- those verbs with subject on the left and object on the right
verbForward :: N3Parser RDFLabel
verbForward =
-- (try (string "=>") *> operatorLabel logImplies)
(string "=>" *> operatorLabel logImplies)
<|> (string "=" *> operatorLabel owlSameAs)
-- <|> (try (atWord "a") *> operatorLabel rdfType)
<|> (atWord "a" *> operatorLabel rdfType)
<|> (atWord "has" *> lexeme expression)
<|> lexeme expression
{-
universal ::= | "@forAll" symbol_csl
TODO: what needs to be done to support universal quantification
-}
universal :: N3Parser ()
universal =
-- try (atWord "forAll") *>
atWord "forAll" *>
failBad "universal (@forAll) currently unsupported."
-- will be something like: *> symbolCsl
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012, 2013, 2014, 2018, 2020 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/RDF/Parser/Turtle.hs 0000644 0000000 0000000 00000074766 14220136201 016636 0 ustar 00 0000000 0000000 {-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
#if (__GLASGOW_HASKELL__ >= 802)
{-# LANGUAGE DerivingStrategies #-}
#endif
--------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : Turtle
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011, 2012, 2013, 2014, 2018, 2020, 2022 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : CPP, DerivingStrategies, OverloadedStrings
--
-- This Module implements a Turtle parser, returning a
-- new 'RDFGraph' consisting of triples and namespace information parsed from
-- the supplied input string, or an error indication.
--
-- REFERENCES:
--
-- - \"Turtle, Terse RDF Triple Language\",
-- W3C Candidate Recommendation 19 February 2013 (
--
-- NOTES:
--
-- - Prior to version @0.9.0.4@, the parser followed the
-- W3C Working Draft 09 August 2011 ()
--
-- - Strings with no language tag are converted to a 'LitTag' not a
-- 'TypedLitTag' with a type of @xsd:string@ (e.g. see
-- ).
--
-- - If the URI is actually an IRI (Internationalized Resource Identifiers)
-- then the parser will fail since 'Network.URI.parseURI' fails.
--
-- - The current (August 2013) Turtle test suite from
-- passes except for the four
-- tests with non-ASCII local names, namely:
-- @localName_with_assigned_nfc_bmp_PN_CHARS_BASE_character_boundaries@,
-- @localName_with_assigned_nfc_PN_CHARS_BASE_character_boundaries@,
-- @localName_with_nfc_PN_CHARS_BASE_character_boundaries@,
-- and
-- @localName_with_non_leading_extras@.
--
--------------------------------------------------------------------------------
-- TODO:
-- - should the productions moved to an Internal module for use by
-- others - e.g. Sparql or the N3 parser?
module Swish.RDF.Parser.Turtle
( ParseResult
, parseTurtle
, parseTurtlefromText
)
where
import Swish.GraphClass (arc)
import Swish.Namespace (Namespace, ScopedName)
import Swish.Namespace (makeNamespace, getNamespaceTuple
, getScopeNamespace, getScopedNameURI
, getScopeNamespace, makeURIScopedName, makeNSScopedName)
import Swish.QName (newLName, emptyLName)
import Swish.RDF.Graph
( RDFGraph, RDFLabel(..)
, NamespaceMap
, addArc
, setNamespaces
, emptyRDFGraph
)
import Swish.RDF.Vocabulary
( LanguageTag
, toLangTag
, rdfType
, rdfFirst, rdfRest, rdfNil
, xsdBoolean, xsdInteger, xsdDecimal, xsdDouble
, defaultBase
)
import Swish.RDF.Datatype (makeDatatypedLiteral)
import Swish.RDF.Parser.Utils
( ParseResult
, runParserWithError
, ignore
, noneOf
, char
, ichar
, string
, stringT
, sepEndBy1
, isymbol
, lexeme
, whiteSpace
, hex4
, hex8
, appendURIs
)
import Control.Applicative
import Control.Monad (foldM)
import Data.Char (chr, isAsciiLower, isAsciiUpper, isDigit, isHexDigit, ord, toLower)
#if MIN_VERSION_base(4, 7, 0)
import Data.Functor (($>))
#endif
import Data.Maybe (fromMaybe)
import Data.Word (Word32)
import Network.URI (URI(..), parseURIReference)
import Text.ParserCombinators.Poly.StateText
import qualified Data.Map as M
import qualified Data.Text as T
import qualified Data.Text.Lazy as L
#if !MIN_VERSION_base(4, 7, 0)
($>) :: Functor f => f a -> b -> f b
($>) = flip (<$)
#endif
----------------------------------------------------------------------
-- Define parser state and helper functions
----------------------------------------------------------------------
-- | Turtle parser state
data TurtleState = TurtleState
{ graphState :: RDFGraph -- Graph under construction
, prefixUris :: NamespaceMap -- namespace prefix mapping table
, baseUri :: URI -- base URI
, nodeGen :: Word32 -- blank node id generator
} deriving
#if (__GLASGOW_HASKELL__ >= 802)
stock
#endif
Show
-- | Functions to update TurtleState vector (use with stUpdate)
setPrefix :: Maybe T.Text -> URI -> TurtleState -> TurtleState
setPrefix pre uri st = st { prefixUris=p' }
where
p' = M.insert pre uri (prefixUris st)
-- | Change the base
setBase :: URI -> TurtleState -> TurtleState
setBase buri st = st { baseUri = buri }
-- Functions to access state:
-- | Return the default prefix
getDefaultPrefix :: TurtleParser Namespace
getDefaultPrefix = do
s <- stGet
case getPrefixURI s Nothing of
Just uri -> return $ makeNamespace Nothing uri
_ -> failBad "No default prefix defined; how unexpected (probably a programming error)!"
-- Map prefix to URI (naming needs a scrub here)
getPrefixURI :: TurtleState -> Maybe T.Text -> Maybe URI
getPrefixURI st pre = M.lookup pre (prefixUris st)
findPrefixNamespace :: Maybe L.Text -> TurtleParser Namespace
findPrefixNamespace (Just p) = findPrefix (L.toStrict p)
findPrefixNamespace Nothing = getDefaultPrefix
-- Return function to update graph in Turtle parser state,
-- using the supplied function of a graph
--
updateGraph :: (RDFGraph -> RDFGraph) -> TurtleState -> TurtleState
updateGraph f s = s { graphState = f (graphState s) }
----------------------------------------------------------------------
-- Define top-level parser function:
-- accepts a string and returns a graph or error
----------------------------------------------------------------------
type TurtleParser a = Parser TurtleState a
-- | Parse as Turtle (with no real base URI).
--
-- See 'parseTurtle' if you need to provide a base URI.
--
parseTurtlefromText ::
L.Text -- ^ input in N3 format.
-> ParseResult
parseTurtlefromText = flip parseTurtle Nothing
-- | Parse a string with an optional base URI.
--
-- Unlike 'parseN3' we treat the base URI as a URI and not
-- a QName.
--
parseTurtle ::
L.Text -- ^ input in N3 format.
-> Maybe URI -- ^ optional base URI
-> ParseResult
parseTurtle txt mbase = parseAnyfromText turtleDoc mbase txt
{-
hashURI :: URI
hashURI = fromJust $ parseURIReference "#"
-}
-- | The W3C turtle tests - e.g. -
-- point out there's no default prefix mapping.
--
emptyState ::
Maybe URI -- ^ starting base for the graph
-> TurtleState
emptyState mbase =
let pmap = M.empty -- M.singleton Nothing hashURI
buri = fromMaybe (getScopedNameURI defaultBase) mbase
in TurtleState
{ graphState = emptyRDFGraph
, prefixUris = pmap
, baseUri = buri
, nodeGen = 0
}
-- | Function to supply initial context and parse supplied term.
--
parseAnyfromText ::
TurtleParser a -- ^ parser to apply
-> Maybe URI -- ^ base URI of the input, or @Nothing@ to use default base value
-> L.Text -- ^ input to be parsed
-> Either String a
parseAnyfromText parser mbase = runParserWithError parser (emptyState mbase)
newBlankNode :: TurtleParser RDFLabel
newBlankNode = do
n <- stQuery (succ . nodeGen)
stUpdate $ \s -> s { nodeGen = n }
return $ Blank (show n)
{-
This has been made tricky by the attempt to remove the default list
of prefixes from the starting point of a parse and the subsequent
attempt to add every new namespace we come across to the parser state.
So we add in the original default namespaces for testing, since
this routine is really for testing.
addTestPrefixes :: TurtleParser ()
addTestPrefixes = stUpdate $ \st -> st { prefixUris = LookupMap prefixTable } -- should append to existing map
-}
-- helper routines
comma, semiColon , fullStop :: TurtleParser ()
comma = isymbol ","
semiColon = isymbol ";"
fullStop = isymbol "."
sQuot, dQuot, sQuot3, dQuot3 :: TurtleParser ()
sQuot = ichar '\''
dQuot = ichar '"'
sQuot3 = ignore $ string "'''"
dQuot3 = ignore $ string "\"\"\""
match :: (Ord a) => a -> [(a,a)] -> Bool
match v = any (\(l,h) -> v >= l && v <= h)
-- a specialization of bracket that ensures white space after
-- the bracket symbol is parsed.
br :: Char -> Char -> TurtleParser a -> TurtleParser a
br lsym rsym =
let f = lexeme . char
in bracket (f lsym) (f rsym)
-- this is a lot simpler than N3
atWord :: T.Text -> TurtleParser ()
atWord s = (char '@' *> lexeme (stringT s)) $> ()
-- | Case insensitive match.
charI ::
Char -- ^ must be upper case
-> TurtleParser Char
charI c = satisfy (`elem` c : [ toLower c ])
-- | Case insensitive match.
stringI ::
String -- ^ must be upper case
-> TurtleParser String
stringI = mapM charI
{-
Add statement to graph in the parser state; there is a special case
for the special-case literals in the grammar since we need to ensure
the necessary namespaces (in other words xsd) are added to the
namespace store.
-}
addStatement :: RDFLabel -> RDFLabel -> RDFLabel -> TurtleParser ()
addStatement s p o@(TypedLit _ dtype) | dtype `elem` [xsdBoolean, xsdInteger, xsdDecimal, xsdDouble] = do
ost <- stGet
let stmt = arc s p o
oldp = prefixUris ost
ogs = graphState ost
(nspre, nsuri) = getNamespaceTuple $ getScopeNamespace dtype
newp = M.insert nspre nsuri oldp
stUpdate $ \st -> st { prefixUris = newp, graphState = addArc stmt ogs }
addStatement s p o = stUpdate (updateGraph (addArc (arc s p o) ))
isaz, isAZ, isaZ, is09, isaZ09 :: Char -> Bool
isaz = isAsciiLower
isAZ = isAsciiUpper
isaZ c = isaz c || isAZ c
is09 = isDigit
isaZ09 c = isaZ c || is09 c
{-
Since operatorLabel can be used to add a label with an
unknown namespace, we need to ensure that the namespace
is added if not known. If the namespace prefix is already
in use then it is over-written (rather than add a new
prefix for the label).
TODO:
- could we use the reverse lookupmap functionality to
find if the given namespace URI is in the namespace
list? If it is, use it's key otherwise do a
mapReplace for the input namespace (updated to use the
Data.Map.Map representation).
-}
operatorLabel :: ScopedName -> TurtleParser RDFLabel
operatorLabel snam = do
st <- stGet
let (pkey, pval) = getNamespaceTuple $ getScopeNamespace snam
opmap = prefixUris st
rval = Res snam
-- TODO: the lookup and the replacement could be fused; it may not
-- even make sense to separate now using a Map
case M.lookup pkey opmap of
Just val | val == pval -> return rval
| otherwise -> do
stUpdate $ \s -> s { prefixUris = M.insert pkey pval opmap }
return rval
_ -> do
stUpdate $ \s -> s { prefixUris = M.insert pkey pval opmap }
return rval
findPrefix :: T.Text -> TurtleParser Namespace
findPrefix pre = do
st <- stGet
case M.lookup (Just pre) (prefixUris st) of
Just uri -> return $ makeNamespace (Just pre) uri
Nothing -> failBad $ "Undefined prefix '" ++ T.unpack pre ++ ":'."
-- | Add the message to the start of the error message if the
-- parser fails (a minor specialization of 'adjustErr').
{-
addErr :: Parser s a -> String -> Parser s a
addErr p m = adjustErr p (m++)
-}
() ::
Parser s a
-> String -- ^ Error message to add (a new line is added after the message)
-> Parser s a
() p m = adjustErr p ((m ++ "\n") ++)
-- Applicative's <* et al are infixl 4, with <|> infixl 3
infixl 4
{-
Syntax productions; the Turtle ENBF grammar elements are from
http://www.w3.org/TR/2013/CR-turtle-20130219/#sec-grammar-grammar
The element names are converted to match Haskell syntax
and idioms where possible:
- camel Case rather than underscores and all upper case
- upper-case identifiers prepended by _ after above form
-}
{-
[1] turtleDoc ::= statement*
-}
turtleDoc :: TurtleParser RDFGraph
turtleDoc = mkGr <$> (whiteSpace *> many statement *> eof *> stGet)
where
mkGr s = setNamespaces (prefixUris s) (graphState s)
{-
[2] statement ::= directive | triples '.'
-}
statement :: TurtleParser ()
statement = directive <|> (triples *> commit fullStop "Missing '.' after a statement.")
{-
[3] directive ::= prefixID | base | sparqlPrefix | sparqlBase
With the addition of sparqlPrefix/sparqlBase (so '.' handling moved
into prefixID/base) may need to adjust use of lexeme.
-}
directive :: TurtleParser ()
directive =
lexeme
(prefixID "Unable to parse @prefix statement."
<|> base "Unable to parse @base statement."
<|> sparqlPrefix "Unable to parse Sparql PREFIX statement."
<|> sparqlBase "Unable to parse Sparql BASE statement.")
{-
[4] prefixID ::= '@prefix' PNAME_NS IRIREF '.'
-}
prefixID :: TurtleParser ()
prefixID = do
atWord "prefix"
p <- commit $ lexeme _pnameNS
u <- lexeme _iriRef
fullStop
stUpdate $ setPrefix (fmap L.toStrict p) u
{-
[5] base ::= '@base' IRIREF '.'
-}
base :: TurtleParser ()
base = do
atWord "base"
b <- commit $ lexeme _iriRef
fullStop
stUpdate $ setBase b
{-
[5s] sparqlBase ::= "BASE" IRIREF
-}
sparqlBase :: TurtleParser ()
sparqlBase = lexeme (stringI "BASE") >> commit _iriRef >>= stUpdate . setBase
{-
[6s] sparqlPrefix ::= "PREFIX" PNAME_NS IRIREF
-}
sparqlPrefix :: TurtleParser ()
sparqlPrefix = do
ignore $ lexeme $ stringI "PREFIX"
p <- commit $ lexeme _pnameNS
u <- lexeme _iriRef
stUpdate $ setPrefix (fmap L.toStrict p) u
{-
[6] triples ::= subject predicateObjectList | blankNodePropertyList predicateObjectList?
-}
triples :: TurtleParser ()
triples =
(subject >>= predicateObjectList)
<|>
(blankNodePropertyList >>= ignore . optional . predicateObjectList)
{-
[7] predicateObjectList ::= verb objectList (';' (verb objectList)?)*
-}
predicateObjectList :: RDFLabel -> TurtleParser ()
predicateObjectList subj =
let term = verb >>= objectList subj
in ignore $ sepEndBy1 term (many1 semiColon)
{-
[8] objectList ::= object (',' object)*
-}
objectList :: RDFLabel -> RDFLabel -> TurtleParser ()
objectList subj prd = sepBy1 object comma >>= mapM_ (addStatement subj prd)
{-
[9] verb ::= predicate | 'a'
-}
verb :: TurtleParser RDFLabel
verb = predicate <|> (lexeme (char 'a') *> operatorLabel rdfType)
{-
[10] subject ::= iri | BlankNode | collection
-}
subject :: TurtleParser RDFLabel
subject = (Res <$> iri) <|> blankNode <|> collection
{-
[11] predicate ::= iri
-}
predicate :: TurtleParser RDFLabel
predicate = Res <$> iri
{-
[12] object ::= iri | BlankNode | collection | blankNodePropertyList | literal
-}
object :: TurtleParser RDFLabel
object = (Res <$> iri) <|> blankNode <|> collection <|>
blankNodePropertyList <|> literal
{-
[13] literal ::= RDFLiteral | NumericLiteral | BooleanLiteral
-}
literal :: TurtleParser RDFLabel
literal = lexeme $ rdfLiteral <|> numericLiteral <|> booleanLiteral
{-
[14] blankNodePropertyList ::= '[' predicateObjectList ']'
-}
blankNodePropertyList :: TurtleParser RDFLabel
blankNodePropertyList = do
bNode <- newBlankNode
br '[' ']' $ lexeme (predicateObjectList bNode)
return bNode
{-
[15] collection ::= '(' object* ')'
-}
collection :: TurtleParser RDFLabel
collection = do
os <- br '(' ')' $ many object
eNode <- operatorLabel rdfNil
case os of
[] -> return eNode
(x:xs) -> do
sNode <- newBlankNode
first <- operatorLabel rdfFirst
addStatement sNode first x
lNode <- foldM addElem sNode xs
rest <- operatorLabel rdfRest
addStatement lNode rest eNode
return sNode
where
addElem prevNode curElem = do
bNode <- newBlankNode
first <- operatorLabel rdfFirst
rest <- operatorLabel rdfRest
addStatement prevNode rest bNode
addStatement bNode first curElem
return bNode
{-
[16] NumericLiteral ::= INTEGER | DECIMAL | DOUBLE
NOTE: We swap the order from this production
I have removed the conversion to a canonical form for
the double production, since it makes running the W3C
tests for Turtle harder (since it assumes that "1E0"
is passed through as is). It is also funny to
create a "canonical" form for only certain data types.
-}
numericLiteral :: TurtleParser RDFLabel
numericLiteral =
let f t v = makeDatatypedLiteral t (L.toStrict v)
in (f xsdDouble <$> _double)
<|>
(f xsdDecimal <$> _decimal)
<|>
(f xsdInteger <$> _integer)
{-
[128s] RDFLiteral ::= String (LANGTAG | '^^' iri)?
TODO: remove 'Lit lbl' form, since dtype=xsd:string in this case.
-}
rdfLiteral :: TurtleParser RDFLabel
rdfLiteral = do
lbl <- L.toStrict <$> turtleString
opt <- optional ((Left <$> (_langTag "Unable to parse the language tag"))
<|>
(string "^^" *> (Right <$> (commit iri "Unable to parse the datatype of the literal"))))
ignore $ optional whiteSpace
return $ case opt of
Just (Left lcode) -> LangLit lbl lcode
Just (Right dtype) -> TypedLit lbl dtype
_ -> Lit lbl
{-
[133s] BooleanLiteral ::= 'true' | 'false'
-}
booleanLiteral :: TurtleParser RDFLabel
booleanLiteral = makeDatatypedLiteral xsdBoolean . T.pack <$> lexeme (string "true" <|> string "false")
{-
[17] String ::= STRING_LITERAL_QUOTE | STRING_LITERAL_SINGLE_QUOTE | STRING_LITERAL_LONG_SINGLE_QUOTE | STRING_LITERAL_LONG_QUOTE
-}
turtleString :: TurtleParser L.Text
turtleString =
lexeme (
_stringLiteralLongQuote <|> _stringLiteralQuote <|>
_stringLiteralLongSingleQuote <|> _stringLiteralSingleQuote
) "Unable to parse a string literal"
{-
[135s] iri ::= IRIREF | PrefixedName
-}
iri :: TurtleParser ScopedName
iri = lexeme (
(makeURIScopedName <$> _iriRef)
<|>
prefixedName)
{-
[136s] PrefixedName ::= PNAME_LN | PNAME_NS
-}
prefixedName :: TurtleParser ScopedName
prefixedName =
_pnameLN <|>
flip makeNSScopedName emptyLName <$> (_pnameNS >>= findPrefixNamespace)
{-
[137s] BlankNode ::= BLANK_NODE_LABEL | ANON
-}
blankNode :: TurtleParser RDFLabel
blankNode = lexeme (_blankNodeLabel <|> _anon)
{--- Productions for terminals ---}
{-
[18] IRIREF ::= '<' ([^#x00-#x20<>\"{}|^`\] | UCHAR)* '>'
-}
_iriRef :: TurtleParser URI
_iriRef = do
-- ignore $ char '<'
-- why a, I using manyFinally' here? '>' shouldn't overlap
-- with iriRefChar.
-- ustr <- manyFinally' iriRefChar (char '>')
ustr <- bracket (char '<') (commit (char '>')) (many iriRefChar)
case parseURIReference ustr of
Nothing -> failBad $ "Invalid URI: <" ++ ustr ++ ">"
Just uref -> do
s <- stGet
either fail return $ appendURIs (baseUri s) uref
iriRefChar :: TurtleParser Char
iriRefChar = satisfy isIRIChar <|> _uchar
isIRIChar :: Char -> Bool
isIRIChar c =
c > chr 0x20
&&
c `notElem` ("<>\"{}|^`\\"::String)
{-
[139s] PNAME_NS ::= PN_PREFIX? ':'
-}
_pnameNS :: TurtleParser (Maybe L.Text)
_pnameNS = optional _pnPrefix <* char ':'
{-
[140s] PNAME_LN ::= PNAME_NS PN_LOCAL
-}
_pnameLN :: TurtleParser ScopedName
_pnameLN = do
ns <- _pnameNS >>= findPrefixNamespace
l <- fmap L.toStrict _pnLocal
case newLName l of
Just lname -> return $ makeNSScopedName ns lname
_ -> fail $ "Invalid local name: '" ++ T.unpack l ++ "'"
{-
[141s] BLANK_NODE_LABEL ::= '_:' (PN_CHARS_U | [0-9]) ((PN_CHARS | '.')* PN_CHARS)?
-}
_blankNodeLabel :: TurtleParser RDFLabel
_blankNodeLabel = do
ignore $ string "_:"
fChar <- _pnCharsU <|> satisfy is09
rest <- _pnRest
return $ Blank $ fChar : L.unpack rest
{-
Extracted from BLANK_NODE_LABEL and PN_PREFIX
:== ( ( PN_CHARS | '.' )* PN_CHARS )?
We assume below that the match is only ever done for small strings, so
the cost isn't likely to be large. Let's see how well this assumption
holds up.
-}
_pnRest :: TurtleParser L.Text
_pnRest = noTrailingDot _pnChars
{-
There are two productions which look like
( (parser | '.')* parser )?
Unfortunately one of them has parser returning a Char and the
other has the parser returning multiple characters, so separate
out for now; hopefully can combine
Have decided to try replacing this with sepEndBy1, treating the '.'
as a separator, since this is closer to the EBNF. However, this
then eats up multiple '.' characters.
noTrailingDot ::
TurtleParser Char -- ^ This *should not* match '.'
-> TurtleParser L.Text
noTrailingDot p = do
terms <- sepEndBy1 (many p) (char '.')
return $ L.pack $ intercalate "." terms
noTrailingDotM ::
TurtleParser L.Text -- ^ This *should not* match '.'
-> TurtleParser L.Text
noTrailingDotM p = do
terms <- sepEndBy1 (many p) (char '.')
return $ L.intercalate "." $ map L.concat terms
-}
noTrailing ::
TurtleParser a -- ^ parser for '.'
-> ([a] -> String) -- ^ Collect fragments into a string
-> TurtleParser a -- ^ This *should not* match '.'
-> TurtleParser L.Text
noTrailing dotParser conv parser = do
lbl <- many (parser <|> dotParser)
let (nret, lclean) = clean $ conv lbl
-- a simple difference list implementation
edl = id
snocdl x xs = xs . (x:)
appenddl = (.)
replicatedl n x = (replicate n x ++)
-- this started out as a simple automaton/transducer from
-- http://www.haskell.org/pipermail/haskell-cafe/2011-September/095347.html
-- but then I decided to complicate it
--
clean :: String -> (Int, String)
clean = go 0 edl
where
go n acc [] = (n, acc [])
go n acc ('.':xs) = go (n + 1) acc xs
go 0 acc (x:xs) = go 0 (snocdl x acc) xs
go n acc (x:xs) = go 0 (appenddl acc (snocdl x (replicatedl n '.'))) xs
reparse $ L.replicate (fromIntegral nret) "."
return $ L.pack lclean
noTrailingDot ::
TurtleParser Char -- ^ This *should not* match '.'
-> TurtleParser L.Text
noTrailingDot = noTrailing (char '.') id
noTrailingDotM ::
TurtleParser L.Text -- ^ This *should not* match '.'
-> TurtleParser L.Text
noTrailingDotM = noTrailing (char '.' $> ".") (L.unpack . L.concat)
{-
[144s] LANGTAG ::= '@' [a-zA-Z]+ ('-' [a-zA-Z0-9]+)*
Note that toLangTag may fail since it does some extra
validation not done by the parser (mainly on the length of the
primary and secondary tags).
NOTE: This parser does not accept multiple secondary tags which RFC3066
does.
-}
_langTag :: TurtleParser LanguageTag
_langTag = do
ichar '@'
h <- commit $ many1Satisfy isaZ
mt <- optional (L.cons <$> char '-' <*> many1Satisfy isaZ09)
let lbl = L.toStrict $ L.append h $ fromMaybe L.empty mt
case toLangTag lbl of
Just lt -> return lt
_ -> fail ("Invalid language tag: " ++ T.unpack lbl) -- should this be failBad?
-- Returns True for + and False for -.
_leadingSign :: TurtleParser (Maybe Bool)
_leadingSign = do
ms <- optional (satisfy (`elem` ("+-"::String)))
return $ (== '+') `fmap` ms
{-
For when we tried to create a canonical representation.
addSign :: Maybe Bool -> L.Text -> L.Text
addSign (Just False) t = L.cons '-' t
addSign _ t = t
-}
addSign :: Maybe Bool -> L.Text -> L.Text
addSign (Just True) t = L.cons '+' t
addSign (Just _) t = L.cons '-' t
addSign _ t = t
{-
[19] INTEGER ::= [+-]? [0-9]+
We try to produce a canonical form for the
numbers.
-}
_integer :: TurtleParser L.Text
_integer = do
ms <- _leadingSign
rest <- many1Satisfy is09
return $ addSign ms rest
{-
[20] DECIMAL ::= [+-]? [0-9]* '.' [0-9]+
-}
_decimal :: TurtleParser L.Text
_decimal = do
ms <- _leadingSign
leading <- manySatisfy is09
ichar '.'
trailing <- many1Satisfy is09
let ans2 = L.cons '.' trailing
ans = if L.null leading
-- then L.cons '0' ans2 -- create a 'canonical' version
then ans2
else L.append leading ans2
return $ addSign ms ans
{-
[21] DOUBLE ::= [+-]? ([0-9]+ '.' [0-9]* EXPONENT | '.' [0-9]+ EXPONENT | [0-9]+ EXPONENT)
-}
_d1 :: TurtleParser L.Text
_d1 = do
a <- many1Satisfy is09
ichar '.'
b <- manySatisfy is09
return $ a `L.append` ('.' `L.cons` b)
_d2 :: TurtleParser L.Text
_d2 = do
ichar '.'
b <- many1Satisfy is09
return $ '.' `L.cons` b
_d3 :: TurtleParser L.Text
_d3 = many1Satisfy is09
_double :: TurtleParser L.Text
_double = do
ms <- _leadingSign
leading <- _d1 <|> _d2 <|> _d3
e <- _exponent
return $ addSign ms $ leading `L.append` e
{-
[154s] EXPONENT ::= [eE] [+-]? [0-9]+
-}
_exponent :: TurtleParser L.Text
_exponent = do
e <- char 'e' <|> char 'E'
ms <- _leadingSign
L.cons e . addSign ms <$> _integer
{-
[22] STRING_LITERAL_QUOTE ::= '"' ([^#x22#x5C#xA#xD] | ECHAR | UCHAR)* '"'
[23] STRING_LITERAL_SINGLE_QUOTE ::= "'" ([^#x27#x5C#xA#xD] | ECHAR | UCHAR)* "'"
[24] STRING_LITERAL_LONG_SINGLE_QUOTE ::= "'''" (("'" | "''")? [^'\] | ECHAR | UCHAR)* "'''"
[25] STRING_LITERAL_LONG_QUOTE ::= '"""' (('"' | '""')? [^"\] | ECHAR | UCHAR)* '"""'
Since ECHAR | UCHAR is common to all these we pull it out to
create the _protChar parser.
-}
_protChar :: TurtleParser Char
_protChar = char '\\' *> (_echar' <|> _uchar')
_exclSLQ, _exclSLSQ :: String
_exclSLQ = map chr [0x22, 0x5c, 0x0a, 0x0d]
_exclSLSQ = map chr [0x27, 0x5c, 0x0a, 0x0d]
_stringLiteralQuote, _stringLiteralSingleQuote :: TurtleParser L.Text
_stringLiteralQuote = _stringIt dQuot (_tChars _exclSLQ)
_stringLiteralSingleQuote = _stringIt sQuot (_tChars _exclSLSQ)
_stringLiteralLongQuote, _stringLiteralLongSingleQuote :: TurtleParser L.Text
_stringLiteralLongQuote = _stringItLong dQuot3 (_tCharsLong '"')
_stringLiteralLongSingleQuote = _stringItLong sQuot3 (_tCharsLong '\'')
_stringIt :: TurtleParser a -> TurtleParser Char -> TurtleParser L.Text
_stringIt sep chars = L.pack <$> bracket sep sep (many chars)
_stringItLong :: TurtleParser a -> TurtleParser L.Text -> TurtleParser L.Text
_stringItLong sep chars = L.concat <$> bracket sep sep (many chars)
_tChars :: String -> TurtleParser Char
_tChars excl = _protChar <|> noneOf excl
oneOrTwo :: Char -> TurtleParser L.Text
oneOrTwo c = do
ignore $ char c
mb <- optional (char c)
case mb of
Just _ -> return $ L.pack [c,c]
_ -> return $ L.singleton c
_multiQuote :: Char -> TurtleParser L.Text
_multiQuote c = do
mq <- optional (oneOrTwo c)
r <- noneOf (c : "\\")
return $ fromMaybe L.empty mq `L.snoc` r
_tCharsLong :: Char -> TurtleParser L.Text
_tCharsLong c =
L.singleton <$> _protChar
<|> _multiQuote c
{-
[26] UCHAR ::= '\u' HEX HEX HEX HEX | '\U' HEX HEX HEX HEX HEX HEX HEX HEX
-}
_uchar :: TurtleParser Char
_uchar = char '\\' >> _uchar'
_uchar' :: TurtleParser Char
_uchar' =
(char 'u' *> (commit hex4 "Expected 4 hex characters after \\u"))
<|>
(char 'U' *> (commit hex8 "Expected 8 hex characters after \\U"))
{-
[159s] ECHAR ::= '\' [tbnrf\"']
Since ECHAR is only used by the string productions
in the form ECHAR | UCHAR, the check for the leading
\ has been moved out (see _protChar)
_echar :: TurtleParser Char
_echar = char '\\' *> _echar'
-}
_echar' :: TurtleParser Char
_echar' =
(char 't' $> '\t') <|>
(char 'b' $> '\b') <|>
(char 'n' $> '\n') <|>
(char 'r' $> '\r') <|>
(char 'f' $> '\f') <|>
(char '\\' $> '\\') <|>
(char '"' $> '"') <|>
(char '\'' $> '\'')
{-
[161s] WS ::= #x20 | #x9 | #xD | #xA
-}
_ws :: TurtleParser ()
_ws = ignore $ satisfy (`elem` _wsChars)
_wsChars :: String
_wsChars = map chr [0x20, 0x09, 0x0d, 0x0a]
{-
[162s] ANON ::= '[' WS* ']'
-}
_anon :: TurtleParser RDFLabel
_anon =
br '[' ']' (many _ws) >> newBlankNode
{-
[163s] PN_CHARS_BASE ::= [A-Z] | [a-z] | [#x00C0-#x00D6] | [#x00D8-#x00F6] | [#x00F8-#x02FF] | [#x0370-#x037D] | [#x037F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF]
TODO: may want to make this a Char -> Bool selector for
use with manySatisfy rather than a combinator.
-}
_pnCharsBase :: TurtleParser Char
_pnCharsBase =
let f c = let i = ord c
in isaZ c ||
match i [(0xc0, 0xd6), (0xd8, 0xf6), (0xf8, 0x2ff),
(0x370, 0x37d), (0x37f, 0x1fff), (0x200c, 0x200d),
(0x2070, 0x218f), (0x2c00, 0x2fef), (0x3001, 0xd7ff),
(0xf900, 0xfdcf), (0xfdf0, 0xfffd), (0x10000, 0xeffff)]
in satisfy f
{-
[164s] PN_CHARS_U ::= PN_CHARS_BASE | '_'
[166s] PN_CHARS ::= PN_CHARS_U | '-' | [0-9] | #x00B7 | [#x0300-#x036F] | [#x203F-#x2040]
-}
_pnCharsU, _pnChars :: TurtleParser Char
_pnCharsU = _pnCharsBase <|> char '_'
_pnChars =
let f c = let i = ord c
in match i [(0x300, 0x36f), (0x203f, 0x2040)]
in _pnCharsU <|> char '-' <|> satisfy is09 <|>
char (chr 0xb7) <|> satisfy f
{-
[167s] PN_PREFIX ::= PN_CHARS_BASE ((PN_CHARS | '.')* PN_CHARS)?
[168s] PN_LOCAL ::= (PN_CHARS_U | ':' | [0-9] | PLX) ((PN_CHARS | '.' | ':' | PLX)* (PN_CHARS | ':' | PLX))?
-}
_pnPrefix :: TurtleParser L.Text
_pnPrefix = L.cons <$> _pnCharsBase <*> _pnRest
_pnLocal :: TurtleParser L.Text
_pnLocal = do
s <- L.singleton <$> (_pnCharsU <|> char ':' <|> satisfy is09)
<|> _plx
e <- noTrailingDotM (L.singleton <$> (_pnChars <|> char ':') <|> _plx)
return $ s `L.append` e
{-
[169s] PLX ::= PERCENT | PN_LOCAL_ESC
[170s] PERCENT ::= '%' HEX HEX
[171s] HEX ::= [0-9] | [A-F] | [a-f]
[172s] PN_LOCAL_ESC ::= '\' ('_' | '~' | '.' | '-' | '!' | '$' | '&' | "'" | '(' | ')' | '*' | '+' | ',' | ';' | '=' | '/' | '?' | '#' | '@' | '%')
We do not convert hex-encoded values into the characters, which
means we have to deal with Text rather than Char for these
parsers, which is annoying.
-}
_plx, _percent :: TurtleParser L.Text
_plx = _percent <|> (L.singleton <$> _pnLocalEsc)
_percent = do
ichar '%'
a <- _hex
L.cons '%' . L.cons a . L.singleton <$> _hex
_hex, _pnLocalEsc :: TurtleParser Char
_hex = satisfy isHexDigit
_pnLocalEsc = char '\\' *> satisfy (`elem` _pnLocalEscChars)
_pnLocalEscChars :: String
_pnLocalEscChars = "_~.-!$&'()*+,;=/?#@%"
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012, 2013, 2014, 2018, 2020, 2022 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/RDF/Parser/Utils.hs 0000644 0000000 0000000 00000026731 14220136201 016444 0 ustar 00 0000000 0000000 {-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
--------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : Utils
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011, 2012, 2013, 2014, 2018, 2022 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : CPP, OverloadedStrings
--
-- Support for the RDF Parsing modules.
--
--------------------------------------------------------------------------------
module Swish.RDF.Parser.Utils
( SpecialMap
-- , mapPrefix
-- tables
, prefixTable, specialTable
-- parser
, runParserWithError
, ParseResult
, ignore
, char
, ichar
, string
, stringT
, symbol
, isymbol
, lexeme
, notFollowedBy
, whiteSpace
, skipMany
, skipMany1
, endBy
, sepEndBy
, sepEndBy1
, manyTill
, noneOf
, eoln
, fullStop
, hex4
, hex8
, appendURIs
)
where
import Swish.Namespace (Namespace, makeNamespace, ScopedName)
import Swish.RDF.Graph (RDFGraph)
import Swish.RDF.Vocabulary
( namespaceRDF
, namespaceRDFS
, namespaceRDFD
, namespaceOWL
, namespaceLOG
, rdfType
, rdfFirst, rdfRest, rdfNil
, owlSameAs, logImplies
, defaultBase
)
import Data.Char (isSpace, isHexDigit, chr)
#if MIN_VERSION_base(4, 7, 0)
import Data.Functor (($>))
#endif
import Data.Maybe (fromMaybe, fromJust)
import Network.URI (URI(..), relativeTo, parseURIReference)
import Text.ParserCombinators.Poly.StateText
import qualified Data.Map as M
import qualified Data.Text as T
import qualified Data.Text.Lazy as L
import qualified Data.Text.Read as R
#if !MIN_VERSION_base(4, 7, 0)
($>) :: Functor f => f a -> b -> f b
($>) = flip (<$)
#endif
-- Code
-- | Append the two URIs. Given the change in signature of
-- `Network.URI.relativeTo` in version @2.4.0.0@ of @network@,
-- it is not clear that this function is necessary. At the
-- very least, it should be changed to just return a `URI`.
--
appendURIs ::
URI -- ^ The base URI
-> URI -- ^ The URI to append (it can be an absolute URI).
-> Either String URI
appendURIs base uri =
case uriScheme uri of
"" -> Right $ uri `relativeTo` base
_ -> Right uri
-- | Type for special name lookup table
type SpecialMap = M.Map String ScopedName
-- | Define default table of namespaces
prefixTable :: [Namespace]
prefixTable = [ namespaceRDF
, namespaceRDFS
, namespaceRDFD -- datatypes
, namespaceOWL
, namespaceLOG
, makeNamespace Nothing $ fromJust (parseURIReference "#") -- is this correct?
]
-- | Define default special-URI table.
specialTable ::
Maybe ScopedName -- ^ initial base URI, otherwise uses 'defaultBase'
-> [(String,ScopedName)]
specialTable mbase =
[ ("a", rdfType ),
("equals", owlSameAs ),
("implies", logImplies ),
("listfirst", rdfFirst ),
("listrest", rdfRest ),
("listnull", rdfNil ),
("base", fromMaybe defaultBase mbase )
]
-- Parser routines, heavily based on Parsec combinators
-- | Run the parser and return the successful parse or an error
-- message which consists of the standard Polyparse error plus
-- a fragment of the unparsed input to provide context.
--
runParserWithError ::
Parser a b -- ^ parser (carrying state) to apply
-> a -- ^ starting state for the parser
-> L.Text -- ^ input to be parsed
-> Either String b
runParserWithError parser state0 input =
let (result, _, unparsed) = runParser parser state0 input
-- TODO: work out how best to report error context; for now just take the
-- next 40 characters and assume there is enough context.
econtext = if L.null unparsed
then "\n(at end of the text)\n"
else "\nRemaining input:\n" ++
case L.compareLength unparsed 40 of
GT -> L.unpack (L.take 40 unparsed) ++ "..."
_ -> L.unpack unparsed
in case result of
Left emsg -> Left $ emsg ++ econtext
_ -> result
-- | The result of a parse, which is either an error message or a graph.
type ParseResult = Either String RDFGraph
-- | Run the parser and ignore the result.
ignore :: (Applicative f) => f a -> f ()
ignore f = f $> ()
-- | Match the character.
char :: Char -> Parser s Char
char c = satisfy (== c)
-- | Match the character, ignoring the result.
ichar :: Char -> Parser s ()
ichar = ignore . char
-- TODO: is there a better way to do this?
-- | Match the text.
string :: String -> Parser s String
string = mapM char
-- | Match the text.
stringT :: T.Text -> Parser s T.Text
stringT s = string (T.unpack s) >> return s
-- | Run the parser 'many' times and ignore the result.
skipMany :: Parser s a -> Parser s ()
skipMany = ignore . many
-- | Run the parser 'many1' times and ignore the result.
skipMany1 :: Parser s a -> Parser s ()
skipMany1 = ignore . many1
-- | Match zero or more occurences of
-- parser followed by separator.
endBy ::
Parser s a -- ^ parser
-> Parser s b -- ^ separator
-> Parser s [a]
endBy p sep = many (p <* sep)
-- | Match zero or more occurences of the parser followed
-- by the separator.
sepEndBy ::
Parser s a -- ^ parser
-> Parser s b -- ^ separator
-> Parser s [a]
sepEndBy p sep = sepEndBy1 p sep <|> pure []
-- | Accept one or more occurences of the parser
-- separated by the separator. Unlike 'endBy' the
-- last separator is optional.
sepEndBy1 ::
Parser s a -- ^ parser
-> Parser s b -- ^ separator
-> Parser s [a]
sepEndBy1 p sep = do
x <- p
(sep *> ((x:) <$> sepEndBy p sep)) <|> return [x]
-- | Accept zero or more runs of the parser
-- ending with the delimiter.
manyTill ::
Parser s a -- ^ parser
-> Parser s b -- ^ delimiter
-> Parser s [a]
manyTill p end = go
where
go = (end $> [])
<|>
((:) <$> p <*> go)
-- | Accept any character that is not a member of the given string.
noneOf :: String -> Parser s Char
noneOf istr = satisfy (`notElem` istr)
-- | Matches '.'.
fullStop :: Parser s ()
fullStop = ichar '.'
-- | Match the end-of-line sequence (@"\\n"@, @"\\r"@, or @"\\r\\n"@).
eoln :: Parser s ()
-- eoln = ignore (newline <|> (lineFeed *> optional newline))
-- eoln = ignore (try (string "\r\n") <|> string "\r" <|> string "\n")
eoln = ignore (oneOf [string "\r\n", string "\r", string "\n"])
-- | Succeed if the next character does not match the given function.
notFollowedBy :: (Char -> Bool) -> Parser s ()
notFollowedBy p = do
c <- next
if p c
then fail $ "Unexpected character: " ++ show [c]
else reparse $ L.singleton c
-- | Match the given string and any trailing 'whiteSpace'.
symbol :: String -> Parser s String
symbol = lexeme . string
-- | As 'symbol' but ignoring the result.
isymbol :: String -> Parser s ()
isymbol = ignore . symbol
-- | Convert a parser into one that also matches, and ignores,
-- trailing 'whiteSpace'.
lexeme :: Parser s a -> Parser s a
lexeme p = p <* whiteSpace
-- | Match white space: a space or a comment (@#@ character and anything following it
-- up to to a new line).
whiteSpace :: Parser s ()
whiteSpace = skipMany (simpleSpace <|> oneLineComment)
simpleSpace :: Parser s ()
simpleSpace = ignore $ many1Satisfy isSpace
-- TODO: this should use eoln rather than a check on \n
oneLineComment :: Parser s ()
oneLineComment = (ichar '#' *> manySatisfy (/= '\n')) $> ()
{-
Not sure we can get this with polyparse
-- | Annotate a Parsec error with the local context - i.e. the actual text
-- that caused the error and preceeding/succeeding lines (if available)
--
annotateParsecError ::
Int -- ^ the number of extra lines to include in the context (<=0 is ignored)
-> [String] -- ^ text being parsed
-> ParseError -- ^ the parse error
-> String -- ^ Parsec error with additional context
annotateParsecError extraLines ls err =
-- the following is based on the show instance of ParseError
let ePos = errorPos err
lNum = sourceLine ePos
cNum = sourceColumn ePos
-- it is possible to be at the end of the input so need
-- to check; should produce better output than this in this
-- case
nLines = length ls
ln1 = lNum - 1
eln = max 0 extraLines
lNums = [max 0 (ln1 - eln) .. min (nLines-1) (ln1 + eln)]
beforeLines = map (ls !!) $ filter (< ln1) lNums
afterLines = map (ls !!) $ filter (> ln1) lNums
-- in testing was able to get a line number after the text so catch this
-- case; is it still necessary?
errorLine = if ln1 >= nLines then "" else ls !! ln1
arrowLine = replicate (cNum-1) ' ' ++ "^"
finalLine = "(line " ++ show lNum ++ ", column " ++ show cNum ++ " indicated by the '^' sign above):"
eHdr = "" : beforeLines ++ errorLine : arrowLine : afterLines ++ [finalLine]
eMsg = showErrorMessages "or" "unknown parse error" "expecting" "unexpected" "end of input"
(errorMessages err)
in unlines eHdr ++ eMsg
-}
{-
Handle hex encoding; the spec for N3 and NTriples suggest that
only upper-case A..F are valid but you can find lower-case values
out there so support these too.
-}
hexDigit :: Parser a Char
-- hexDigit = satisfy (`elem` ['0'..'9'] ++ ['A'..'F'])
hexDigit = satisfy isHexDigit
-- | A four-digit hex value (e.g. @1a34@ or @03F1@).
hex4 :: Parser a Char
hex4 = do
digs <- exactly 4 hexDigit
let mhex = R.hexadecimal (T.pack digs)
case mhex of
Left emsg -> failBad $ "Internal error: unable to parse hex4: " ++ emsg
Right (v, "") -> return $ chr v
Right (_, vs) -> failBad $ "Internal error: hex4 remainder = " ++ T.unpack vs
-- | An eight-digit hex value that has a maximum of @0010FFFF@.
hex8 :: Parser a Char
hex8 = do
digs <- exactly 8 hexDigit
let mhex = R.hexadecimal (T.pack digs)
case mhex of
Left emsg -> failBad $ "Internal error: unable to parse hex8: " ++ emsg
Right (v, "") -> if v <= 0x10FFFF
then return $ chr v
else failBad "\\UHHHHHHHH format is limited to a maximum of \\U0010FFFF"
Right (_, vs) -> failBad $ "Internal error: hex8 remainder = " ++ T.unpack vs
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012, 2014, 2018, 2022 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/RDF/Proof.hs 0000644 0000000 0000000 00000036477 13543702315 015221 0 ustar 00 0000000 0000000 {-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
--------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : Proof
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012, 2014, 2016 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : CPP, FlexibleInstances, UndecidableInstances
--
-- This module instantiates the 'Proof' framework for
-- constructing proofs over 'RDFGraph' expressions.
-- The intent is that this can be used to test some
-- correspondences between the RDF Model theory and
-- corresponding proof theory based on closure rules
-- applied to the graph, per .
--
--------------------------------------------------------------------------------
module Swish.RDF.Proof
( RDFProof, RDFProofStep
, makeRDFProof, makeRDFProofStep
, makeRdfInstanceEntailmentRule
, makeRdfSubgraphEntailmentRule
, makeRdfSimpleEntailmentRule )
where
import Swish.GraphClass (Label(..), LDGraph(..))
import Swish.Namespace (ScopedName)
import Swish.Proof (Proof(..), Step(..))
import Swish.Rule (Expression(..), Rule(..))
import Swish.VarBinding (makeVarBinding)
import Swish.RDF.Graph (RDFLabel(..), RDFGraph, fmapNSGraph)
import Swish.RDF.Graph (merge, allLabels, remapLabelList)
import Swish.RDF.Query (rdfQueryInstance, rdfQuerySubs)
import Swish.RDF.Ruleset (RDFFormula, RDFRule, RDFRuleset)
import Swish.Utils.ListHelpers (flist)
import Data.List (subsequences)
#if (!defined(__GLASGOW_HASKELL__)) || (__GLASGOW_HASKELL__ < 710)
import Data.Monoid (Monoid(..))
#endif
import qualified Data.Map as M
import qualified Data.Set as S
------------------------------------------------------------
-- Type instantiation of Proof framework for RDFGraph data
------------------------------------------------------------
--
-- This is a partial instantiation of the proof framework.
-- Details for applying inference rules are specific to the
-- graph instance type.
------------------------------------------------------------
-- Proof datatypes for graph values
------------------------------------------------------------
-- The following is an orphan instance
-- |Instances of 'LDGraph' are also instance of the
-- @Expression@ class, for which proofs can be constructed.
-- The empty RDF graph is always @True@ (other enduring
-- truths are asserted as axioms).
instance (LDGraph lg lb, Eq (lg lb)) => Expression (lg lb) where
isValid = S.null . getArcs
------------------------------------------------------------
-- Define RDF-specific types for proof framework
------------------------------------------------------------
-- | An RDF proof.
type RDFProof = Proof RDFGraph
-- | A step in an RDF proof.
type RDFProofStep = Step RDFGraph
------------------------------------------------------------
-- Helper functions for constructing proofs on RDF graphs
------------------------------------------------------------
-- |Make an RDF graph proof step.
--
makeRDFProofStep ::
RDFRule -- ^ rule to use for this step
-> [RDFFormula] -- ^ antecedent RDF formulae for this step
-> RDFFormula -- ^ RDF formula that is the consequent for this step
-> RDFProofStep
makeRDFProofStep rul ants con = Step
{ stepRule = rul
, stepAnt = ants
, stepCon = con
}
-- |Make an RDF proof.
--
makeRDFProof ::
[RDFRuleset] -- ^ RDF rulesets that constitute a proof context for this proof
-> RDFFormula -- ^ initial statement from which the goal is claimed to be proven
-> RDFFormula -- ^ statement that is claimed to be proven
-> [RDFProofStep] -- ^ the chain of inference rules in the proof.
-> RDFProof
makeRDFProof rsets base goal steps = Proof
{ proofContext = rsets
, proofInput = base
, proofResult = goal
, proofChain = steps
}
------------------------------------------------------------
-- RDF instance entailment inference rule
------------------------------------------------------------
-- |Make an inference rule dealing with RDF instance entailment;
-- i.e. entailments that are due to replacement of a URI or literal
-- node with a blank node.
--
-- The part of this rule expected to be useful is 'checkInference'.
-- The 'fwdApply' and 'bwdApply' functions defined here may return
-- rather large results if applied to graphs with many variables or
-- a large vocabulary, and are defined for experimentation.
--
-- Forward and backward chaining is performed with respect to a
-- specified vocabulary. In the case of backward chaining, it would
-- otherwise be impossible to bound the options thus generated.
-- In the case of forward chaining, it is often not desirable to
-- have the properties generalized. If forward or backward backward
-- chaining will not be used, supply an empty vocabulary.
-- Note: graph method 'Swish.RDF.Graph.allNodes' can be used to obtain a list of all
-- the subjects and objects used in a graph, not counting nested
-- formulae; use a call of the form:
--
-- > allNodes (not . labelIsVar) graph
--
makeRdfInstanceEntailmentRule ::
ScopedName -- ^ name
-> [RDFLabel] -- ^ vocabulary
-> RDFRule
makeRdfInstanceEntailmentRule name vocab = newrule
where
newrule = Rule
{ ruleName = name
, fwdApply = rdfInstanceEntailFwdApply vocab
, bwdApply = rdfInstanceEntailBwdApply vocab
, checkInference = rdfInstanceEntailCheckInference
}
-- Instance entailment forward chaining
--
-- Note: unless the initial graph is small, the total result
-- here could be very large. The existential generalizations are
-- sequenced in increasing number of substitutions applied.
--
-- The instances generated are all copies of the merge of the
-- supplied graphs, with some or all of the non-variable nodes
-- replaced by blank nodes.
rdfInstanceEntailFwdApply :: [RDFLabel] -> [RDFGraph] -> [RDFGraph]
rdfInstanceEntailFwdApply vocab ante =
let
-- Merge antecedents to single graph, renaming bnodes if needed.
-- (Null test and using 'foldl1' to avoid merging if possible.)
mergeGraph = if null ante then mempty
else foldl1 merge ante
-- Obtain lists of variable and non-variable nodes
-- (was: nonvarNodes = allLabels (not . labelIsVar) mergeGraph)
nonvarNodes = vocab
varNodes = S.toList $ allLabels labelIsVar mergeGraph
-- Obtain list of possible remappings for non-variable nodes
mapList = remapLabelList nonvarNodes varNodes
mapSubLists = (tail . subsequences) mapList
mapGr ls = fmapNSGraph
(\l -> M.findWithDefault l l
(M.fromList ls))
in
-- Return all remappings of the original merged graph
flist (map mapGr mapSubLists) mergeGraph
-- Instance entailment backward chaining (for specified vocabulary)
--
-- [[[TODO: this is an incomplete implementation, there being no
-- provision for instantiating some variables and leaving others
-- alone. This can be overcome in many cases by combining instance
-- and subgraph chaining.
-- Also, there is no provision for instantiating some variables in
-- a triple and leaving others alone. This may be fixed later if
-- this function is really needed to be completely faithful to the
-- precise notion of instance entailment.]]]
rdfInstanceEntailBwdApply :: [RDFLabel] -> RDFGraph -> [[RDFGraph]]
rdfInstanceEntailBwdApply vocab cons =
let
-- Obtain list of variable nodes
varNodes = S.toList $ allLabels labelIsVar cons
-- Generate a substitution for each combination of variable
-- and vocabulary node.
varBindings = map (makeVarBinding . zip varNodes) vocSequences
vocSequences = powerSequencesLen (length varNodes) vocab
in
-- Generate a substitution for each combination of variable
-- and vocabulary:
[ rdfQuerySubs [v] cons | v <- varBindings ]
-- Instance entailment inference checker
rdfInstanceEntailCheckInference :: [RDFGraph] -> RDFGraph -> Bool
rdfInstanceEntailCheckInference ante cons =
let
mante = if null ante then mempty -- merged antecedents
else foldl1 merge ante
qvars = rdfQueryInstance cons mante -- all query matches
bsubs = rdfQuerySubs qvars cons -- all back substitutions
in
-- Return True if any back-substitution matches the original
-- merged antecendent graph.
elem mante bsubs
------------------------------------------------------------
-- Powersequence (?) -- all sequences from some base values
------------------------------------------------------------
-- |Construct list of lists of sequences of increasing length
powerSeqBylen :: [a] -> [[a]] -> [[[a]]]
powerSeqBylen rs ps = ps : powerSeqBylen rs (powerSeqNext rs ps)
-- |Return sequences of length n+1 given original sequence
-- and list of all sequences of length n
powerSeqNext :: [a] -> [[a]] -> [[a]]
powerSeqNext rs rss = [ h:t | t <- rss, h <- rs ]
-- |Return all powersequences of a given length
powerSequencesLen :: Int -> [a] -> [[a]]
powerSequencesLen len rs = powerSeqBylen rs [[]] !! len
-- Instance entailment notes.
--
-- Relation to simple entailment (s-entails):
--
-- (1) back-substitution yields original graph
-- ex:s1 ex:p1 ex:o1 s-entails ex:s1 ex:p1 _:o1 by [_:o1/ex:o1]
--
-- (2) back-substitution yields original graph
-- ex:s1 ex:p1 ex:o1 s-entails ex:s1 ex:p1 _:o2 by [_:o2/ex:o1]
-- ex:s1 ex:p1 _:o1 ex:s1 ex:p1 _:o3 [_:o3/_:o1]
--
-- (3) back-substitution does not yield original graph
-- ex:s1 ex:p1 ex:o1 s-entails ex:s1 ex:p1 _:o2 by [_:o2/ex:o1]
-- ex:s1 ex:p1 _:o1 ex:s1 ex:p1 _:o3 [_:o3/ex:o1]
--
-- (4) consider
-- ex:s1 ex:p1 ex:o1 s-entails ex:s1 ex:p1 ex:o1
-- ex:s1 ex:p1 ex:o2 ex:s1 ex:p1 ex:o2
-- ex:s1 ex:p1 ex:o3 ex:s1 ex:p1 _:o1
-- ex:s1 ex:p1 _:o2
-- where [_:o1/ex:o1,_:o2/ex:o2] yields a simple entailment but not
-- an instance entailment, but [_:o1/ex:o3,_:o2/ex:o3] is also
-- (arguably) an instance entailment. Therefore, it is not sufficient
-- to look only at the "largest" substitutions to determine instance
-- entailment.
--
-- All this means that when checking for instance entailment by
-- back substitution, all of the query results must be checked.
-- This seems clumsy. If this function is heavily used with
-- multiple query matches, a modified query that uses each
-- triple of the target graph exactly once may be required.
------------------------------------------------------------
-- RDF subgraph entailment inference rule
------------------------------------------------------------
-- |Make an inference rule dealing with RDF subgraph entailment.
-- The part of this rule expected to be useful is 'checkInference'.
-- The 'fwdApply' function defined here may return rather large
-- results. But in the name of completeness and experimentation
-- with the possibilities of lazy evaluation, it has been defined.
--
-- Backward chaining is not performed, as there is no reasonable way
-- to choose a meaningful supergraph of that supplied.
makeRdfSubgraphEntailmentRule :: ScopedName -> RDFRule
makeRdfSubgraphEntailmentRule name = newrule
where
newrule = Rule
{ ruleName = name
, fwdApply = rdfSubgraphEntailFwdApply
, bwdApply = const []
, checkInference = rdfSubgraphEntailCheckInference
}
-- Subgraph entailment forward chaining
--
-- Note: unless the initial graph is small, the total result
-- here could be very large. The subgraphs are sequenced in
-- increasing size of the sub graph.
rdfSubgraphEntailFwdApply :: [RDFGraph] -> [RDFGraph]
rdfSubgraphEntailFwdApply ante =
let
-- Merge antecedents to single graph, renaming bnodes if needed.
-- (Null test and using 'foldl1' to avoid merging if possible.)
mergeGraph = if null ante then mempty
else foldl1 merge ante
in
-- Return all subgraphs of the full graph constructed above
-- TODO: update to use sets as appropriate
map (setArcs mergeGraph . S.fromList) (init $ tail $ subsequences $ S.toList $ getArcs mergeGraph)
-- Subgraph entailment inference checker
--
-- This is of dubious utiltiy, as it doesn't allow for node renaming.
-- The simple entailment inference rule is probably more useful here.
rdfSubgraphEntailCheckInference :: [RDFGraph] -> RDFGraph -> Bool
rdfSubgraphEntailCheckInference ante cons =
let
-- Combine antecedents to single graph, renaming bnodes if needed.
-- (Null test and using 'foldl1' to avoid merging if possible.)
fullGraph = if null ante then mempty
else foldl1 addGraphs ante
in
-- Check each consequent graph arc is in the antecedent graph
getArcs cons `S.isSubsetOf` getArcs fullGraph
------------------------------------------------------------
-- RDF simple entailment inference rule
------------------------------------------------------------
-- |Make an inference rule dealing with RDF simple entailment.
-- The part of this rule expected to be useful is 'checkInference'.
-- The 'fwdApply' and 'bwdApply' functions defined return null
-- results, indicating that they are not useful for the purposes
-- of proof discovery.
makeRdfSimpleEntailmentRule :: ScopedName -> RDFRule
makeRdfSimpleEntailmentRule name = newrule
where
newrule = Rule
{ ruleName = name
, fwdApply = const []
, bwdApply = const []
, checkInference = rdfSimpleEntailCheckInference
}
-- Simple entailment inference checker
--
-- Note: antecedents here are presumed to share bnodes.
-- (Use 'merge' instead of 'add' for non-shared bnodes)
--
rdfSimpleEntailCheckInference :: [RDFGraph] -> RDFGraph -> Bool
rdfSimpleEntailCheckInference ante cons =
let agr = if null ante then mempty else foldl1 addGraphs ante
in not $ null $ rdfQueryInstance cons agr
{- original..
not $ null $ rdfQueryInstance cons (foldl1 merge ante)
-}
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/RDF/ProofContext.hs 0000644 0000000 0000000 00000065446 13543702315 016564 0 ustar 00 0000000 0000000 {-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
--------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : ProofContext
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011, 2012, 2014 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : CPP, OverloadedStrings
--
-- This module contains proof-context declarations based on
-- the RDF, RDFS, and RDF datatyping semantics specifications.
-- These definitions consist of namespaces (for identification
-- in proofs), axioms and inference rules.
--
--------------------------------------------------------------------------------
module Swish.RDF.ProofContext ( rulesetRDF
, rulesetRDFS
, rulesetRDFD) where
import Swish.Datatype (typeMkCanonicalForm)
import Swish.Namespace (Namespace, makeNSScopedName)
import Swish.QName (LName)
import Swish.Ruleset (makeRuleset)
import Swish.VarBinding (VarBindingModify(..))
import Swish.VarBinding (applyVarBinding, addVarBinding, makeVarFilterModify, varFilterDisjunction)
import Swish.RDF.BuiltIn.Datatypes (findRDFDatatype)
import Swish.RDF.Proof (makeRdfSubgraphEntailmentRule
, makeRdfSimpleEntailmentRule )
import Swish.RDF.Ruleset
( RDFFormula, RDFRule, RDFRuleset
, makeRDFFormula
, makeN3ClosureRule
, makeN3ClosureSimpleRule
, makeN3ClosureModifyRule
, makeN3ClosureAllocatorRule
, makeNodeAllocTo )
import Swish.RDF.VarBinding
( RDFVarBinding
, RDFVarBindingModify
, RDFVarBindingFilter
, rdfVarBindingUriRef, rdfVarBindingBlank
, rdfVarBindingLiteral
, rdfVarBindingUntypedLiteral
, rdfVarBindingXMLLiteral, rdfVarBindingDatatyped
, rdfVarBindingMemberProp
)
import Swish.RDF.Graph (RDFLabel(..), isUri)
import Swish.RDF.Vocabulary
( namespaceRDFD
, scopeRDF
, scopeRDFS
, scopeRDFD
)
#if (!defined(__GLASGOW_HASKELL__)) || (__GLASGOW_HASKELL__ < 710)
import Data.Monoid (Monoid(..))
#endif
import qualified Data.Text.Lazy.Builder as B
------------------------------------------------------------
-- Define query binding filter auxiliaries
------------------------------------------------------------
makeFormula :: Namespace -> LName -> B.Builder -> RDFFormula
makeFormula = makeRDFFormula
requireAny :: [RDFVarBindingFilter] -> RDFVarBindingFilter
requireAny = varFilterDisjunction
isLiteralV :: String -> RDFVarBindingFilter
isLiteralV = rdfVarBindingLiteral . Var
isUntypedLitV :: String -> RDFVarBindingFilter
isUntypedLitV = rdfVarBindingUntypedLiteral . Var
isXMLLitV :: String -> RDFVarBindingFilter
isXMLLitV = rdfVarBindingXMLLiteral . Var
isUriRefV :: String -> RDFVarBindingFilter
isUriRefV = rdfVarBindingUriRef . Var
isBlankV :: String -> RDFVarBindingFilter
isBlankV = rdfVarBindingBlank . Var
isDatatypedV :: String -> String -> RDFVarBindingFilter
isDatatypedV d l = rdfVarBindingDatatyped (Var d) (Var l)
isMemberPropV :: String -> RDFVarBindingFilter
isMemberPropV = rdfVarBindingMemberProp . Var
allocateTo :: String -> String -> [RDFLabel] -> RDFVarBindingModify
allocateTo bv av = makeNodeAllocTo (Var bv) (Var av)
-- Create new binding for datatype
valueSame :: String -> String -> String -> String -> RDFVarBindingModify
valueSame val1 typ1 val2 typ2 =
sameDatatypedValue (Var val1) (Var typ1) (Var val2) (Var typ2)
-- Variable binding modifier to create new binding to a canonical
-- form of a datatyped literal.
sameDatatypedValue ::
RDFLabel -> RDFLabel -> RDFLabel -> RDFLabel -> RDFVarBindingModify
sameDatatypedValue val1 typ1 val2 typ2 = VarBindingModify
{ vbmName = makeNSScopedName namespaceRDFD "sameValue"
, vbmApply = sameDatatypedValueApplyAll val1 typ1 val2 typ2
, vbmVocab = [val1,typ1,val2,typ2]
, vbmUsage = [[val2]]
}
sameDatatypedValueApplyAll ::
RDFLabel -> RDFLabel -> RDFLabel -> RDFLabel
-> [RDFVarBinding]
-> [RDFVarBinding]
sameDatatypedValueApplyAll val1 typ1 val2 typ2 =
concatMap (sameDatatypedValueApply val1 typ1 val2 typ2)
-- Auxiliary function that handles variable binding updates
-- for sameDatatypedValue
sameDatatypedValueApply ::
RDFLabel -> RDFLabel -> RDFLabel -> RDFLabel
-> RDFVarBinding
-> [RDFVarBinding]
sameDatatypedValueApply val1 typ1 val2 typ2 vbind =
result
where
v1 = applyVarBinding vbind val1
t1 = applyVarBinding vbind typ1
t2 = applyVarBinding vbind typ2
sametype = getCanonical v1 t1 t2
result =
if isUri t1 && isUri t2 then
if t1 == t2 then
case sametype of
Just st -> [addVarBinding val2 st vbind]
_ -> []
else
error "subtype conversions not yet defined"
else
[]
{-
getCanonical :: RDFLabel -> RDFLabel -> RDFLabel -> Maybe RDFLabel
getCanonical v1 t1 t2 =
if isDatatyped dqn1 v1 && isJust mdt1 then
liftM mkLit $ typeMkCanonicalForm dt1 (getLiteralText v1)
else
Nothing
where
dqn1 = getRes t1
dqn2 = getRes t2
mdt1 = findRDFDatatype dqn1
dt1 = fromJust mdt1
mkLit = flip Lit (Just dqn2)
getRes (Res dqnam) = dqnam
getRes x = error $ "Expected a Resource, sent " ++ show x -- for -Wall
-}
getCanonical :: RDFLabel -> RDFLabel -> RDFLabel -> Maybe RDFLabel
getCanonical (TypedLit v dt) (Res dqn1) (Res dqn2) =
if dt == dqn1
then case findRDFDatatype dqn1 of
Just dt1 -> flip TypedLit dqn2 `fmap` typeMkCanonicalForm dt1 v
_ -> Nothing
else Nothing
getCanonical _ _ _ = Nothing
{- -- Test data
qnamint = ScopedName namespaceXSD "integer"
xsdint = Res qnamint
lab010 = Lit "010" (Just qnamint)
can010 = getCanonical lab010 xsdint xsdint
nsex = Namespace "ex" "http://example.org/"
resexp = Res (ScopedName nsex "p")
resexs = Res (ScopedName nsex "s")
vara = Var "a"
varb = Var "b"
varc = Var "c"
vard = Var "d"
varp = Var "p"
vars = Var "s"
vart = Var "t"
vb1 = makeVarBinding [(vara,lab010),(varb,xsdint),(vard,xsdint)]
vb2 = sameDatatypedValueApply vara varb varc vard vb1
vb3 = vbmApply (sameDatatypedValue vara varb varc vard) [vb1]
vb3t = vb3 == vb2
vb4 = vbmApply (valueSame "a" "b" "c" "d") [vb1]
vb4t = vb4 == vb2
vb5 = vbmApply (valueSame "a" "b" "c" "b") [vb1]
vb5t = vb5 == vb2
vb6 = makeVarBinding [(vars,lab010),(varp,resexp),(vara,resexs),(vard,xsdint)]
vb7 = vbmApply (valueSame "s" "d" "t" "d") [vb6]
vb8 = makeVarBinding [(vars,lab010),(varp,resexp),(vara,resexs),(vard,xsdint)
,(vart,fromJust can010)]
vb8t = vb7 == [vb8]
-- -}
------------------------------------------------------------
-- Common definitions
------------------------------------------------------------
------------------------------------------------------------
-- Define RDF axioms
------------------------------------------------------------
-- scopeRDF = Namespace "rs-rdf" "http://id.ninebynine.org/2003/Ruleset/rdf#"
-- RDF axioms (from RDF semantics document, section 3.1)
--
-- (See also, container property rules below)
--
rdfa1 :: RDFFormula
rdfa1 = makeFormula scopeRDF "a1" "rdf:type rdf:type rdf:Property ."
rdfa2 :: RDFFormula
rdfa2 = makeFormula scopeRDF "a2" "rdf:subject rdf:type rdf:Property ."
rdfa3 :: RDFFormula
rdfa3 = makeFormula scopeRDF "a3" "rdf:predicate rdf:type rdf:Property ."
rdfa4 :: RDFFormula
rdfa4 = makeFormula scopeRDF "a4" "rdf:object rdf:type rdf:Property ."
rdfa5 :: RDFFormula
rdfa5 = makeFormula scopeRDF "a5" "rdf:first rdf:type rdf:Property ."
rdfa6 :: RDFFormula
rdfa6 = makeFormula scopeRDF "a6" "rdf:rest rdf:type rdf:Property ."
rdfa7 :: RDFFormula
rdfa7 = makeFormula scopeRDF "a7" "rdf:value rdf:type rdf:Property ."
rdfa8 :: RDFFormula
rdfa8 = makeFormula scopeRDF "a8" "rdf:nil rdf:type rdf:List ."
axiomsRDF :: [RDFFormula]
axiomsRDF =
[ rdfa1, rdfa2, rdfa3, rdfa4, rdfa5
, rdfa6, rdfa7, rdfa8
]
------------------------------------------------------------
-- Define RDF rules
------------------------------------------------------------
-- RDF subgraph entailment (from RDF semantics document section 2)
--
rdfsub :: RDFRule
rdfsub = makeRdfSubgraphEntailmentRule (makeNSScopedName scopeRDF "sub")
-- RDF simple entailment (from RDF semantics document section 7.1)
-- (Note: rules se1 and se2 are combined here, because the scope of
-- the "allocatedTo" modifier is the application of a single rule.)
--
rdfse :: RDFRule
rdfse = makeRdfSimpleEntailmentRule (makeNSScopedName scopeRDF "se")
-- RDF bnode-for-literal assignments (from RDF semantics document section 7.1)
--
rdflg :: RDFRule
rdflg = makeN3ClosureAllocatorRule scopeRDF "lg"
"?x ?a ?l . "
"?x ?a ?b . ?b rdf:_allocatedTo ?l ."
(makeVarFilterModify $ isLiteralV "l")
(allocateTo "b" "l")
-- RDF bnode-for-literal back-tracking (from RDF semantics document section 7.1)
--
rdfgl :: RDFRule
rdfgl = makeN3ClosureSimpleRule scopeRDF "gl"
"?x ?a ?l . ?b rdf:_allocatedTo ?l . "
"?x ?a ?b ."
-- RDF entailment rules (from RDF semantics document section 7.2)
--
-- (Note, statements with property rdf:_allocatedTo are introduced to
-- track bnodes introduced according to rule rdflf [presumably this
-- is actually rdflg?])
--
rdfr1 :: RDFRule
rdfr1 = makeN3ClosureSimpleRule scopeRDF "r1"
"?x ?a ?y ."
"?a rdf:type rdf:Property ."
rdfr2 :: RDFRule
rdfr2 = makeN3ClosureRule scopeRDF "r2"
"?x ?a ?b . ?b rdf:_allocatedTo ?l . "
"?b rdf:type rdf:XMLLiteral ."
(makeVarFilterModify $ isXMLLitV "l")
-- Container property axioms (from RDF semantics document section 3.1)
--
-- (Using here an inference rule with a filter in place of an axiom schema)
--
-- This is a restricted form of the given axioms, in that the axioms
-- are asserted only for container membership terms that appear in
-- the graph.
--
-- (This may be very inefficient for forward chaining when dealing with
-- large graphs: may need to look at query logic to see if the search for
-- container membership properties can be optimized. This may call for a
-- custom inference rule.)
--
rdfcp1 :: RDFRule
rdfcp1 = makeN3ClosureRule scopeRDF "cp1"
"?x ?c ?y . "
"?c rdf:type rdf:Property ."
(makeVarFilterModify $ isMemberPropV "c")
rdfcp2 :: RDFRule
rdfcp2 = makeN3ClosureRule scopeRDF "cp2"
"?c ?p ?y . "
"?c rdf:type rdf:Property ."
(makeVarFilterModify $ isMemberPropV "c")
rdfcp3 :: RDFRule
rdfcp3 = makeN3ClosureRule scopeRDF "cp3"
"?x ?p ?c . "
"?c rdf:type rdf:Property ."
(makeVarFilterModify $ isMemberPropV "c")
-- Collect RDF rules
--
rulesRDF :: [RDFRule]
rulesRDF =
[ rdfsub, rdfse
, rdflg, rdfgl
, rdfr1, rdfr2
, rdfcp1, rdfcp2, rdfcp3
]
-- | Ruleset for RDF inference.
rulesetRDF :: RDFRuleset
rulesetRDF = makeRuleset scopeRDF axiomsRDF rulesRDF
------------------------------------------------------------
-- Define RDFS axioms
------------------------------------------------------------
-- scopeRDFS = Namespace "rdfs" "http://id.ninebynine.org/2003/Ruleset/rdfs#"
-- RDFS axioms (from RDF semantics document, section 4.1)
--
-- (See also, container property rules below)
--
rdfsa01 :: RDFFormula
rdfsa01 = makeFormula scopeRDFS "a01"
"rdf:type rdfs:domain rdfs:Resource ."
rdfsa02 :: RDFFormula
rdfsa02 = makeFormula scopeRDFS "a02"
"rdf:type rdfs:range rdfs:Class ."
rdfsa03 :: RDFFormula
rdfsa03 = makeFormula scopeRDFS "a03"
"rdfs:domain rdfs:domain rdf:Property ."
rdfsa04 :: RDFFormula
rdfsa04 = makeFormula scopeRDFS "a04"
"rdfs:domain rdfs:range rdfs:Class ."
rdfsa05 :: RDFFormula
rdfsa05 = makeFormula scopeRDFS "a05"
"rdfs:range rdfs:domain rdf:Property ."
rdfsa06 :: RDFFormula
rdfsa06 = makeFormula scopeRDFS "a06"
"rdfs:range rdfs:range rdfs:Class ."
rdfsa07 :: RDFFormula
rdfsa07 = makeFormula scopeRDFS "a07"
"rdfs:subPropertyOf rdfs:domain rdf:Property ."
rdfsa08 :: RDFFormula
rdfsa08 = makeFormula scopeRDFS "a08"
"rdfs:subPropertyOf rdfs:range rdf:Property ."
rdfsa09 :: RDFFormula
rdfsa09 = makeFormula scopeRDFS "a09"
"rdfs:subClassOf rdfs:domain rdfs:Class ."
rdfsa10 :: RDFFormula
rdfsa10 = makeFormula scopeRDFS "a10"
"rdfs:subClassOf rdfs:range rdfs:Class ."
rdfsa11 :: RDFFormula
rdfsa11 = makeFormula scopeRDFS "a11"
"rdf:subject rdfs:domain rdf:Statement ."
rdfsa12 :: RDFFormula
rdfsa12 = makeFormula scopeRDFS "a12"
"rdf:subject rdfs:range rdfs:Resource ."
rdfsa13 :: RDFFormula
rdfsa13 = makeFormula scopeRDFS "a13"
"rdf:predicate rdfs:domain rdf:Statement ."
rdfsa14 :: RDFFormula
rdfsa14 = makeFormula scopeRDFS "a14"
"rdf:predicate rdfs:range rdfs:Resource ."
rdfsa15 :: RDFFormula
rdfsa15 = makeFormula scopeRDFS "a15"
"rdf:object rdfs:domain rdf:Statement ."
rdfsa16 :: RDFFormula
rdfsa16 = makeFormula scopeRDFS "a16"
"rdf:object rdfs:range rdfs:Resource ."
rdfsa17 :: RDFFormula
rdfsa17 = makeFormula scopeRDFS "a17"
"rdfs:member rdfs:domain rdfs:Resource ."
rdfsa18 :: RDFFormula
rdfsa18 = makeFormula scopeRDFS "a18"
"rdfs:member rdfs:range rdfs:Resource ."
rdfsa19 :: RDFFormula
rdfsa19 = makeFormula scopeRDFS "a19"
"rdf:first rdfs:domain rdf:List ."
rdfsa20 :: RDFFormula
rdfsa20 = makeFormula scopeRDFS "a20"
"rdf:first rdfs:range rdfs:Resource ."
rdfsa21 :: RDFFormula
rdfsa21 = makeFormula scopeRDFS "a21"
"rdf:rest rdfs:domain rdf:List ."
rdfsa22 :: RDFFormula
rdfsa22 = makeFormula scopeRDFS "a22"
"rdf:rest rdfs:range rdf:List ."
rdfsa23 :: RDFFormula
rdfsa23 = makeFormula scopeRDFS "a23"
"rdfs:seeAlso rdfs:domain rdfs:Resource ."
rdfsa24 :: RDFFormula
rdfsa24 = makeFormula scopeRDFS "a24"
"rdfs:seeAlso rdfs:range rdfs:Resource ."
rdfsa25 :: RDFFormula
rdfsa25 = makeFormula scopeRDFS "a25"
"rdfs:isDefinedBy rdfs:domain rdfs:Resource ."
rdfsa26 :: RDFFormula
rdfsa26 = makeFormula scopeRDFS "a26"
"rdfs:isDefinedBy rdfs:range rdfs:Resource ."
rdfsa27 :: RDFFormula
rdfsa27 = makeFormula scopeRDFS "a27"
"rdfs:isDefinedBy rdfs:subPropertyOf rdfs:seeAlso ."
rdfsa28 :: RDFFormula
rdfsa28 = makeFormula scopeRDFS "a28"
"rdfs:comment rdfs:domain rdfs:Resource ."
rdfsa29 :: RDFFormula
rdfsa29 = makeFormula scopeRDFS "a29"
"rdfs:comment rdfs:range rdfs:Literal ."
rdfsa30 :: RDFFormula
rdfsa30 = makeFormula scopeRDFS "a30"
"rdfs:label rdfs:domain rdfs:Resource ."
rdfsa31 :: RDFFormula
rdfsa31 = makeFormula scopeRDFS "a31"
"rdfs:label rdfs:range rdfs:Literal ."
rdfsa32 :: RDFFormula
rdfsa32 = makeFormula scopeRDFS "a32"
"rdf:value rdfs:domain rdfs:Resource ."
rdfsa33 :: RDFFormula
rdfsa33 = makeFormula scopeRDFS "a33"
"rdf:value rdfs:range rdfs:Resource ."
rdfsa34 :: RDFFormula
rdfsa34 = makeFormula scopeRDFS "a34"
"rdf:Alt rdfs:subClassOf rdfs:Container ."
rdfsa35 :: RDFFormula
rdfsa35 = makeFormula scopeRDFS "a35"
"rdf:Bag rdfs:subClassOf rdfs:Container ."
rdfsa36 :: RDFFormula
rdfsa36 = makeFormula scopeRDFS "a36"
"rdf:Seq rdfs:subClassOf rdfs:Container ."
rdfsa37 :: RDFFormula
rdfsa37 = makeFormula scopeRDFS "a37"
"rdfs:ContainerMembershipProperty rdfs:subClassOf rdf:Property ."
rdfsa38 :: RDFFormula
rdfsa38 = makeFormula scopeRDFS "a38"
"rdf:XMLLiteral rdf:type rdfs:Datatype ."
rdfsa39 :: RDFFormula
rdfsa39 = makeFormula scopeRDFS "a39"
"rdf:XMLLiteral rdfs:subClassOf rdfs:Literal ."
rdfsa40 :: RDFFormula
rdfsa40 = makeFormula scopeRDFS "a40"
"rdfs:Datatype rdfs:subClassOf rdfs:Class ."
axiomsRDFS :: [RDFFormula]
axiomsRDFS =
[ rdfsa01, rdfsa02, rdfsa03, rdfsa04
, rdfsa05, rdfsa06, rdfsa07, rdfsa08, rdfsa09
, rdfsa10, rdfsa11, rdfsa12, rdfsa13, rdfsa14
, rdfsa15, rdfsa16, rdfsa17, rdfsa18, rdfsa19
, rdfsa20, rdfsa21, rdfsa22, rdfsa23, rdfsa24
, rdfsa25, rdfsa26, rdfsa27, rdfsa28, rdfsa29
, rdfsa30, rdfsa31, rdfsa32, rdfsa33, rdfsa34
, rdfsa35, rdfsa36, rdfsa37, rdfsa38, rdfsa39
, rdfsa40
]
------------------------------------------------------------
-- Define RDFS rules
------------------------------------------------------------
{-
rdfr2 = makeN3ClosureRule scopeRDF "r2"
"?x ?a ?b . ?b rdf:_allocatedTo ?l . "
"?b rdf:type rdf:XMLLiteral ."
(makeVarFilterModify $ isXMLLit "?l")
-}
-- RDFS entailment rules (from RDF semantics document section 7.2)
--
-- (Note, statements with property rdf:_allocatedTo are introduced to
-- track bnodes introduced according to rule rdflf [presumably this
-- is actually rdflg?])
--
rdfsr1 :: RDFRule
rdfsr1 = makeN3ClosureRule scopeRDFS "r1"
"?x ?a ?b . ?b rdf:_allocatedTo ?l . "
"?b rdf:type rdfs:Literal ."
(makeVarFilterModify $ isUntypedLitV "l" )
rdfsr2 :: RDFRule
rdfsr2 = makeN3ClosureSimpleRule scopeRDFS "r2"
"?x ?a ?y . ?a rdfs:domain ?z ."
"?x rdf:type ?z ."
rdfsr3 :: RDFRule
rdfsr3 = makeN3ClosureRule scopeRDFS "r3"
"?u ?a ?v . ?a rdfs:range ?z ."
"?v rdf:type ?z ."
(makeVarFilterModify $ requireAny [isUriRefV "v",isBlankV "v"])
rdfsr4a :: RDFRule
rdfsr4a = makeN3ClosureSimpleRule scopeRDFS "r4a"
"?x ?a ?y ."
"?x rdf:type rdfs:Resource ."
rdfsr4b :: RDFRule
rdfsr4b = makeN3ClosureRule scopeRDFS "r4b"
"?x ?a ?u ."
"?u rdf:type rdfs:Resource ."
(makeVarFilterModify $ requireAny [isUriRefV "u",isBlankV "u"])
rdfsr5 :: RDFRule
rdfsr5 = makeN3ClosureSimpleRule scopeRDFS "r5"
"?a rdfs:subPropertyOf ?b . ?b rdfs:subPropertyOf ?c ."
"?a rdfs:subPropertyOf ?c ."
rdfsr6 :: RDFRule
rdfsr6 = makeN3ClosureSimpleRule scopeRDFS "r6"
"?x rdf:type rdf:Property ."
"?x rdfs:subPropertyOf ?x ."
rdfsr7 :: RDFRule
rdfsr7 = makeN3ClosureSimpleRule scopeRDFS "r7"
"?x ?a ?y . ?a rdfs:subPropertyOf ?b ."
"?x ?b ?y ."
rdfsr8 :: RDFRule
rdfsr8 = makeN3ClosureSimpleRule scopeRDFS "r8"
"?x rdf:type rdfs:Class ."
"?x rdfs:subClassOf rdfs:Resource ."
rdfsr9 :: RDFRule
rdfsr9 = makeN3ClosureSimpleRule scopeRDFS "r9"
"?x rdfs:subClassOf ?y . ?a rdf:type ?x ."
"?a rdf:type ?y ."
rdfsr10 :: RDFRule
rdfsr10 = makeN3ClosureSimpleRule scopeRDFS "r10"
"?x rdf:type rdfs:Class ."
"?x rdfs:subClassOf ?x ."
rdfsr11 :: RDFRule
rdfsr11 = makeN3ClosureSimpleRule scopeRDFS "r11"
"?x rdfs:subClassOf ?y . ?y rdfs:subClassOf ?z ."
"?x rdfs:subClassOf ?z ."
rdfsr12 :: RDFRule
rdfsr12 = makeN3ClosureSimpleRule scopeRDFS "r12"
"?x rdf:type rdfs:ContainerMembershipProperty ."
"?x rdfs:subPropertyOf rdfs:member ."
rdfsr13 :: RDFRule
rdfsr13 = makeN3ClosureSimpleRule scopeRDFS "r13"
"?x rdf:type rdfs:Datatype ."
"?x rdfs:subClassOf rdfs:Literal ."
-- These are valid only under an extensional strengthening of RDFS,
-- discussed in section 7.3.1 of the RDF semantics specification:
{-
rdfsrext1 :: RDFRule
rdfsrext1 = makeN3ClosureSimpleRule scopeRDFS "ext1"
"?x rdfs:domain ?y . ?y rdfs:subClassOf ?z ."
"?x rdfs:domain ?z ."
rdfsrext2 :: RDFRule
rdfsrext2 = makeN3ClosureSimpleRule scopeRDFS "ext2"
"?x rdfs:range ?y . ?y rdfs:subClassOf ?z ."
"?x rdfs:range ?z ."
rdfsrext3 :: RDFRule
rdfsrext3 = makeN3ClosureSimpleRule scopeRDFS "ext3"
"?x rdfs:domain ?y . ?z rdfs:subPropertyOf ?x ."
"?z rdfs:domain ?y ."
rdfsrext4 :: RDFRule
rdfsrext4 = makeN3ClosureSimpleRule scopeRDFS "ext4"
"?x rdfs:range ?y . ?z rdfs:subPropertyOf ?x ."
"?z rdfs:range ?y ."
rdfsrext5 :: RDFRule
rdfsrext5 = makeN3ClosureSimpleRule scopeRDFS "ext5"
"rdf:type rdfs:subPropertyOf ?z . ?z rdfs:domain ?y ."
"rdfs:Resource rdfs:subClassOf ?y ."
rdfsrext6 :: RDFRule
rdfsrext6 = makeN3ClosureSimpleRule scopeRDFS "rext6"
"rdfs:subClassOf rdfs:subPropertyOf ?z . ?z rdfs:domain ?y ."
"rdfs:Class rdfs:subClassOf ?y ."
rdfsrext7 :: RDFRule
rdfsrext7 = makeN3ClosureSimpleRule scopeRDFS "rext7"
"rdfs:subPropertyOf rdfs:subPropertyOf ?z . ?z rdfs:domain ?y ."
"rdfs:Property rdfs:subClassOf ?y ."
rdfsrext8 :: RDFRule
rdfsrext8 = makeN3ClosureSimpleRule scopeRDFS "rext8"
"rdfs:subClassOf rdfs:subPropertyOf ?z . ?z rdfs:range ?y ."
"rdfs:Class rdfs:subClassOf ?y ."
rdfsrext9 :: RDFRule
rdfsrext9 = makeN3ClosureSimpleRule scopeRDFS "rext9"
"rdfs:subPropertyOf rdfs:subPropertyOf ?z . ?z rdfs:range ?y ."
"rdfs:Property rdfs:subClassOf ?y ."
-}
-- Container property axioms (from RDF semantics document section 4.1)
--
-- (Using here an inference rule with a filter in place of an axiom schema)
--
-- This is a restricted form of the given axioms, in that the axioms
-- are asserted only for container membership terms that appear in
-- the graph.
--
-- (This may be very inefficient for forward chaining when dealing with
-- large graphs: may need to look at query logic to see if the search for
-- container membership properties can be optimized. This may call for a
-- custom inference rule.)
--
rdfscp11 :: RDFRule
rdfscp11 = makeN3ClosureRule scopeRDFS "cp11"
"?x ?c ?y . "
"?c rdf:type rdfs:ContainerMembershipProperty ."
(makeVarFilterModify $ isMemberPropV "c")
rdfscp12 :: RDFRule
rdfscp12 = makeN3ClosureRule scopeRDFS "cp12"
"?c ?p ?y . "
"?c rdf:type rdfs:ContainerMembershipProperty ."
(makeVarFilterModify $ isMemberPropV "c")
rdfscp13 :: RDFRule
rdfscp13 = makeN3ClosureRule scopeRDFS "cp13"
"?x ?p ?c . "
"?c rdf:type rdfs:ContainerMembershipProperty ."
(makeVarFilterModify $ isMemberPropV "c")
rdfscp21 :: RDFRule
rdfscp21 = makeN3ClosureRule scopeRDFS "cp21"
"?x ?c ?y . "
"?c rdfs:domain rdfs:Resource ."
(makeVarFilterModify $ isMemberPropV "c")
rdfscp22 :: RDFRule
rdfscp22 = makeN3ClosureRule scopeRDFS "cp22"
"?c ?p ?y . "
"?c rdfs:domain rdfs:Resource ."
(makeVarFilterModify $ isMemberPropV "c")
rdfscp23 :: RDFRule
rdfscp23 = makeN3ClosureRule scopeRDFS "cp23"
"?x ?p ?c . "
"?c rdfs:domain rdfs:Resource ."
(makeVarFilterModify $ isMemberPropV "c")
rdfscp31 :: RDFRule
rdfscp31 = makeN3ClosureRule scopeRDFS "cp31"
"?x ?c ?y . "
"?c rdfs:range rdfs:Resource ."
(makeVarFilterModify $ isMemberPropV "c")
rdfscp32 :: RDFRule
rdfscp32 = makeN3ClosureRule scopeRDFS "cp32"
"?c ?p ?y . "
"?c rdfs:range rdfs:Resource ."
(makeVarFilterModify $ isMemberPropV "c")
rdfscp33 :: RDFRule
rdfscp33 = makeN3ClosureRule scopeRDFS "cp33"
"?x ?p ?c . "
"?c rdfs:range rdfs:Resource ."
(makeVarFilterModify $ isMemberPropV "c")
-- Collect RDFS rules
--
rulesRDFS :: [RDFRule]
rulesRDFS =
[ rdfsr1, rdfsr2, rdfsr3, rdfsr4a, rdfsr4b
, rdfsr5, rdfsr6, rdfsr7, rdfsr8, rdfsr9
, rdfsr10, rdfsr11, rdfsr12, rdfsr13
, rdfscp11, rdfscp12, rdfscp13
, rdfscp21, rdfscp22, rdfscp23
, rdfscp31, rdfscp32, rdfscp33
]
-- | Ruleset for RDFS inference.
rulesetRDFS :: RDFRuleset
rulesetRDFS = makeRuleset scopeRDFS axiomsRDFS rulesRDFS
------------------------------------------------------------
-- Define RDFD (datatyping) axioms
------------------------------------------------------------
-- scopeRDFD = Namespace "rdfd" "http://id.ninebynine.org/2003/Ruleset/rdfd#"
axiomsRDFD :: [RDFFormula]
axiomsRDFD =
[
]
------------------------------------------------------------
-- Define RDFD (datatyping) axioms
------------------------------------------------------------
-- RDFD closure rules from semantics document, section 7.4
-- Infer type of datatyped literal
--
rdfdr1 :: RDFRule
rdfdr1 = makeN3ClosureRule scopeRDFD "r1"
"?d rdf:type rdfs:Datatype . ?a ?p ?l . ?b rdf:_allocatedTo ?l . "
"?b rdf:type ?d ."
(makeVarFilterModify $ isDatatypedV "d" "l")
-- Equivalent literals with same datatype:
-- (generate canonical form, or operate in proof mode only)
--
rdfdr2 :: RDFRule
rdfdr2 = makeN3ClosureRule scopeRDFD "r2"
"?d rdf:type rdfs:Datatype . ?a ?p ?s ."
"?a ?p ?t ."
(valueSame "s" "d" "t" "d")
{- Note that valueSame does datatype check. Otherwise use:
rdfdr2 = makeN3ClosureModifyRule scopeRDFD "r2"
"?d rdf:type rdfs:Datatype . ?a ?p ?s ."
"?a ?p ?t ."
(makeVarFilterModify $ isDatatypedV "d" "s")
(valueSame "s" "d" "t" "d")
-}
-- Equivalent literals with different datatypes:
-- (generate canonical form, or operate in proof mode only)
--
rdfdr3 :: RDFRule
rdfdr3 = makeN3ClosureModifyRule scopeRDFD "r3"
( "?d rdf:type rdfs:Datatype . ?e rdf:type rdfs:Datatype . " `mappend`
"?a ?p ?s ." )
"?a ?p ?t ."
(makeVarFilterModify $ isDatatypedV "s" "d")
(valueSame "s" "d" "t" "e")
-- Collect RDFD rules
--
rulesRDFD :: [RDFRule]
rulesRDFD =
[ rdfdr1, rdfdr2, rdfdr3
]
-- | Ruleset for RDFD (datatyping) inference.
--
rulesetRDFD :: RDFRuleset
rulesetRDFD = makeRuleset scopeRDFD axiomsRDFD rulesRDFD
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/RDF/Query.hs 0000644 0000000 0000000 00000061135 14220136201 015212 0 ustar 00 0000000 0000000 {-# LANGUAGE CPP #-}
--------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : Query
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011, 2012, 2014, 2022 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : CPP
--
-- This module defines functions for querying an RDF graph to obtain
-- a set of variable substitutions, and to apply a set of variable
-- substitutions to a query pattern to obtain a new graph.
--
-- It also defines a few primitive graph access functions.
--
-- A minimal example is shown below, where we query a very simple
-- graph:
--
-- >>> :set -XOverloadedStrings
-- >>> import Swish.RDF
-- >>> import Swish.RDF.Parser.N3 (parseN3fromText)
-- >>> import Swish.RDF.Query
-- >>> import Swish.VarBinding (VarBinding(vbMap))
-- >>> import Network.URI (parseURI)
-- >>> import Data.Maybe (fromJust, mapMaybe)
-- >>> let qparse = either error id . parseN3fromText
-- >>> let igr = qparse "@prefix a: . a:a a a:A ; a:foo a:bar. a:b a a:B ; a:foo a:bar."
-- >>> let qgr = qparse "?node a ?type."
-- >>> let ans = rdfQueryFind qgr igr
-- >>> :t ans
-- ans :: [Swish.RDF.VarBinding.RDFVarBinding]
-- >>> ans
-- [[(?node,a:a),(?type,a:A)],[(?node,a:b),(?type,a:B)]]
-- >>> let bn = toRDFLabel . fromJust . parseURI $ "http://example.com/B"
-- >>> let arcs = rdfFindArcs (rdfObjEq bn) igr
-- >>> :t arcs
-- arcs :: [RDFTriple]
-- >>> arcs
-- [(a:b,rdf:type,a:B)]
-- >>> let lbls = mapMaybe (`vbMap` (Var "type")) ans
-- >>> :t lbls
-- lbls :: [RDFLabel]
-- >>> lbls
-- [a:A,a:B]
--
--------------------------------------------------------------------------------
module Swish.RDF.Query
( rdfQueryFind, rdfQueryFilter
, rdfQueryBack, rdfQueryBackFilter, rdfQueryBackModify
, rdfQueryInstance
, rdfQuerySubs, rdfQueryBackSubs
, rdfQuerySubsAll
, rdfQuerySubsBlank, rdfQueryBackSubsBlank
, rdfFindArcs, rdfSubjEq, rdfPredEq, rdfObjEq
, rdfFindPredVal, rdfFindPredInt, rdfFindValSubj
, rdfFindList
-- * Utility routines
, allp
, anyp
-- * Exported for testing
, rdfQuerySubs2 )
where
import Swish.Datatype (DatatypeMap(..))
import Swish.VarBinding (VarBinding(..), VarBindingModify(..), VarBindingFilter(..))
import Swish.VarBinding (makeVarBinding, applyVarBinding, joinVarBindings)
import Swish.RDF.Graph
( Arc(..), LDGraph(..)
, arcSubj, arcPred, arcObj
, RDFLabel(..)
, isDatatyped, isBlank, isQueryVar
, getLiteralText, makeBlank
, RDFTriple
, RDFGraph
, allLabels, remapLabels
, resRdfFirst
, resRdfRest
, resRdfNil
, traverseNSGraph
)
import Swish.RDF.VarBinding (RDFVarBinding, RDFVarBindingFilter)
import Swish.RDF.VarBinding (nullRDFVarBinding)
import Swish.RDF.Datatype.XSD.MapInteger (mapXsdInteger)
import Swish.RDF.Vocabulary (xsdInteger, xsdNonNegInteger)
import Swish.Utils.ListHelpers (flist)
import Control.Monad (when)
import Control.Monad.State (State, runState, modify)
import Data.Maybe (mapMaybe, isJust, fromJust)
#if (!defined(__GLASGOW_HASKELL__)) || (__GLASGOW_HASKELL__ < 710)
import Data.Monoid (Monoid(..))
#endif
import qualified Data.Set as S
------------------------------------------------------------
-- Primitive RDF graph queries
------------------------------------------------------------
-- Get a list of arcs from a graph.
--
-- Can we update the routines to work with sets instead?
getTriples :: RDFGraph -> [RDFTriple]
getTriples = S.toList . getArcs
-- | Basic graph-query function.
--
-- The triples of the query graph are matched sequentially
-- against the target graph, each taking account of any
-- variable bindings that have already been determined,
-- and adding new variable bindings as triples containing
-- query variables are matched against the graph.
--
rdfQueryFind ::
RDFGraph -- ^ The query graph.
-> RDFGraph -- ^ The target graph.
-> [RDFVarBinding]
-- ^ Each element represents a set of variable bindings that make the query graph a
-- subgraph of the target graph. The list can be empty.
rdfQueryFind =
rdfQueryPrim1 matchQueryVariable nullRDFVarBinding . getTriples
-- Helper function to match query against a graph.
-- A node-query function is supplied to determine how query nodes
-- are matched against target graph nodes. Also supplied is
-- an initial variable binding.
--
rdfQueryPrim1 ::
NodeQuery RDFLabel -> RDFVarBinding -> [Arc RDFLabel]
-> RDFGraph
-> [RDFVarBinding]
rdfQueryPrim1 _ initv [] _ = [initv]
rdfQueryPrim1 nodeq initv (qa:qas) tg =
let qam = fmap (applyVarBinding initv) qa -- subst vars already bound
newv = rdfQueryPrim2 nodeq qam tg -- new bindings, or null
in concat
[ rdfQueryPrim1 nodeq v2 qas tg
| v1 <- newv
, let v2 = joinVarBindings initv v1
]
-- Match single query term against graph, and return any new sets
-- of variable bindings thus defined, or [] if the query term
-- cannot be matched. Each of the RDFVarBinding values returned
-- represents an alternative possible match for the query arc.
--
rdfQueryPrim2 ::
NodeQuery RDFLabel -> Arc RDFLabel
-> RDFGraph
-> [RDFVarBinding]
rdfQueryPrim2 nodeq qa tg =
mapMaybe (getBinding nodeq qa) (S.toList $ getArcs tg)
-- |RDF query filter.
--
-- This function applies a supplied query binding
-- filter to the result from a call of 'rdfQueryFind'.
--
-- If none of the query bindings found satisfy the filter, a null
-- list is returned (which is what 'rdfQueryFind' returns if the
-- query cannot be satisfied).
--
-- (Because of lazy evaluation, this should be as efficient as
-- applying the filter as the search proceeds. I started to build
-- the filter logic into the query function itself, with consequent
-- increase in complexity, until I remembered lazy evaluation lets
-- me keep things separate.)
--
rdfQueryFilter ::
RDFVarBindingFilter -> [RDFVarBinding] -> [RDFVarBinding]
rdfQueryFilter qbf = filter (vbfTest qbf)
------------------------------------------------------------
-- Backward-chaining RDF graph queries
------------------------------------------------------------
-- |Reverse graph-query function.
--
-- Similar to 'rdfQueryFind', but with different success criteria.
-- The query graph is matched against the supplied graph,
-- but not every triple of the query is required to be matched.
-- Rather, every triple of the target graph must be matched,
-- and substitutions for just the variables thus bound are
-- returned. In effect, these are subsitutions in the query
-- that entail the target graph (where @rdfQueryFind@ returns
-- substitutions that are entailed by the target graph).
--
-- Multiple substitutions may be used together, so the result
-- returned is a list of lists of query bindings. Each inner
-- list contains several variable bindings that must all be applied
-- separately to the closure antecendents to obtain a collection of
-- expressions that together are antecedent to the supplied
-- conclusion. A null list of bindings returned means the
-- conclusion can be inferred without any antecedents.
--
-- Note: in back-chaining, the conditions required to prove each
-- target triple are derived independently, using the inference rule
-- for each such triple, so there are no requirements to check
-- consistency with previously determined variable bindings, as
-- there are when doing forward chaining. A result of this is that
-- there may be redundant triples generated by the back-chaining
-- process. Any process using back-chaining should deal with the
-- results returned accordingly.
--
-- An empty outer list is returned if no combination of
-- substitutions can infer the supplied target.
--
rdfQueryBack ::
RDFGraph -- ^ Query graph
-> RDFGraph -- ^ Target graph
-> [[RDFVarBinding]]
rdfQueryBack qg tg =
let ga = getTriples
in rdfQueryBack1 matchQueryVariable [] (ga qg) (ga tg)
rdfQueryBack1 ::
NodeQuery RDFLabel -> [RDFVarBinding] -> [Arc RDFLabel] -> [Arc RDFLabel]
-> [[RDFVarBinding]]
rdfQueryBack1 _ initv _ [] = [initv]
rdfQueryBack1 nodeq initv qas (ta:tas) = concat
[ rdfQueryBack1 nodeq (nv:initv) qas tas
| nv <- rdfQueryBack2 nodeq qas ta ]
-- Match a query against a single graph term, and return any new sets of
-- variable bindings thus defined. Each member of the result is an
-- alternative possible set of variable bindings. An empty list returned
-- means no match.
--
rdfQueryBack2 ::
NodeQuery RDFLabel -> [Arc RDFLabel] -> Arc RDFLabel
-> [RDFVarBinding]
rdfQueryBack2 nodeq qas ta =
[ fromJust b | qa <- qas, let b = getBinding nodeq qa ta, isJust b ]
-- |RDF back-chaining query filter. This function applies a supplied
-- query binding filter to the result from a call of 'rdfQueryBack'.
--
-- Each inner list contains bindings that must all be used to satisfy
-- the backchain query, so if any query binding does not satisfy the
-- filter, the entire corresponding row is removed
rdfQueryBackFilter ::
RDFVarBindingFilter -> [[RDFVarBinding]] -> [[RDFVarBinding]]
rdfQueryBackFilter qbf = filter (all (vbfTest qbf))
-- |RDF back-chaining query modifier. This function applies a supplied
-- query binding modifier to the result from a call of 'rdfQueryBack'.
--
-- Each inner list contains bindings that must all be used to satisfy
-- a backchaining query, so if any query binding does not satisfy the
-- filter, the entire corresponding row is removed
--
rdfQueryBackModify ::
VarBindingModify a b -> [[VarBinding a b]] -> [[VarBinding a b]]
rdfQueryBackModify qbm = concatMap (rdfQueryBackModify1 qbm)
-- Auxiliary back-chaining query variable binding modifier function:
-- for a supplied list of variable bindings, all of which must be used
-- together when backchaining:
-- (a) make each list member into a singleton list
-- (b) apply the binding modifier to each such list, which may result
-- in a list with zero, one or more elements.
-- (c) return the sequence of these, each member of which is
-- an alternative list of variable bindings, where the members of
-- each alternative must be used together.
--
rdfQueryBackModify1 ::
VarBindingModify a b -> [VarBinding a b] -> [[VarBinding a b]]
rdfQueryBackModify1 qbm = mapM (vbmApply qbm . (:[]))
------------------------------------------------------------
-- Simple entailment graph query
------------------------------------------------------------
-- |Simple entailment (instance) graph query.
--
-- This function queries a graph to find instances of the
-- query graph in the target graph. It is very similar
-- to the normal forward chaining query 'rdfQueryFind',
-- except that blank nodes rather than query variable nodes
-- in the query graph are matched against nodes in the target
-- graph. Neither graph should contain query variables.
--
-- An instance is defined by the RDF semantics specification,
-- per , and is obtained by replacing
-- blank nodes with URIs, literals or other blank nodes. RDF
-- simple entailment can be determined in terms of instances.
-- This function looks for a subgraph of the target graph that
-- is an instance of the query graph, which is a necessary and
-- sufficient condition for RDF entailment (see the Interpolation
-- Lemma in RDF Semantics, section 1.2).
--
-- It is anticipated that this query function can be used in
-- conjunction with backward chaining to determine when the
-- search for sufficient antecendents to determine some goal
-- has been concluded.
rdfQueryInstance :: RDFGraph -> RDFGraph -> [RDFVarBinding]
rdfQueryInstance =
rdfQueryPrim1 matchQueryBnode nullRDFVarBinding . getTriples
------------------------------------------------------------
-- Primitive RDF graph query support functions
------------------------------------------------------------
-- |Type of query node testing function. Return value is:
--
-- * @Nothing@ if no match
--
-- * @Just True@ if match with new variable binding
--
-- * @Just False@ if match with new variable binding
--
type NodeQuery a = a -> a -> Maybe Bool
-- Extract query binding from matching a single query triple with a
-- target triple, returning:
-- - Nothing if the query is not matched
-- - Just nullVarBinding if there are no new variable bindings
-- - Just binding is a new query binding for this match
getBinding ::
NodeQuery RDFLabel -> Arc RDFLabel -> Arc RDFLabel
-> Maybe RDFVarBinding
getBinding nodeq (Arc s1 p1 o1) (Arc s2 p2 o2) =
makeBinding [(s1,s2),(p1,p2),(o1,o2)] []
where
makeBinding [] bs = Just $ makeVarBinding bs
makeBinding (vr@(v,r):bvrs) bs =
case nodeq v r of
Nothing -> Nothing
Just False -> makeBinding bvrs bs
Just True -> makeBinding bvrs (vr:bs)
-- Match variable node against target node, returning
-- Nothing if they do not match, Just True if a variable
-- node is matched (thereby creating a new variable binding)
-- or Just False if a non-blank node is matched.
matchQueryVariable :: NodeQuery RDFLabel
matchQueryVariable (Var _) _ = Just True
matchQueryVariable q t
| q == t = Just False
| otherwise = Nothing
-- Match blank query node against target node, returning
-- Nothing if they do not match, Just True if a blank node
-- is matched (thereby creating a new equivalence) or
-- Just False if a non-blank node is matched.
matchQueryBnode :: NodeQuery RDFLabel
matchQueryBnode (Blank _) _ = Just True
matchQueryBnode q t
| q == t = Just False
| otherwise = Nothing
------------------------------------------------------------
-- Substitute results from RDF query back into a graph
------------------------------------------------------------
-- |Graph substitution function.
--
-- Uses the supplied variable bindings to substitute variables in
-- a supplied graph, returning a list of result graphs corresponding
-- to each set of variable bindings applied to the input graph.
-- This function is used for formward chaining substitutions, and
-- returns only those result graphs for which all query variables
-- are bound.
rdfQuerySubs :: [RDFVarBinding] -> RDFGraph -> [RDFGraph]
rdfQuerySubs vars gr =
map fst $ filter (null . snd) $ rdfQuerySubsAll vars gr
-- |Graph back-substitution function.
--
-- Uses the supplied variable bindings from 'rdfQueryBack' to perform
-- a series of variable substitutions in a supplied graph, returning
-- a list of lists of result graphs corresponding to each set of variable
-- bindings applied to the input graphs.
--
-- The outer list of the result contains alternative antecedent lists
-- that satisfy the query goal. Each inner list contains graphs that
-- must all be inferred to satisfy the query goal.
rdfQueryBackSubs ::
[[RDFVarBinding]] -> RDFGraph -> [[(RDFGraph,[RDFLabel])]]
rdfQueryBackSubs varss gr = [ rdfQuerySubsAll v gr | v <- varss ]
-- |Graph substitution function.
--
-- This function performs the substitutions and returns a list of
-- result graphs each paired with a list unbound variables in each.
rdfQuerySubsAll :: [RDFVarBinding] -> RDFGraph -> [(RDFGraph,[RDFLabel])]
rdfQuerySubsAll vars gr = [ rdfQuerySubs2 v gr | v <- vars ]
-- |Graph substitution function.
--
-- This function performs each of the substitutions in 'vars', and
-- replaces any nodes corresponding to unbound query variables
-- with new blank nodes.
rdfQuerySubsBlank :: [RDFVarBinding] -> RDFGraph -> [RDFGraph]
rdfQuerySubsBlank vars gr =
[ remapLabels vs bs makeBlank g
| v <- vars
, let (g,vs) = rdfQuerySubs2 v gr
, let bs = S.toList $ allLabels isBlank g
]
-- |Graph back-substitution function, replacing variables with bnodes.
--
-- Uses the supplied variable bindings from 'rdfQueryBack' to perform
-- a series of variable substitutions in a supplied graph, returning
-- a list of lists of result graphs corresponding to each set of variable
-- bindings applied to the input graphs.
--
-- The outer list of the result contains alternative antecedent lists
-- that satisfy the query goal. Each inner list contains graphs that
-- must all be inferred to satisfy the query goal.
rdfQueryBackSubsBlank :: [[RDFVarBinding]] -> RDFGraph -> [[RDFGraph]]
rdfQueryBackSubsBlank varss gr = [ rdfQuerySubsBlank v gr | v <- varss ]
-- |This function applies a substitution for a single set of variable
-- bindings, returning the result and a list of unbound variables.
-- It uses a state transformer monad to collect the list of
-- unbound variables.
--
-- Adding an empty graph forces elimination of duplicate arcs.
rdfQuerySubs2 :: RDFVarBinding -> RDFGraph -> (RDFGraph, [RDFLabel])
rdfQuerySubs2 varb gr = (addGraphs mempty g, S.toList vs) -- the addgraphs part is important, possibly just to remove duplicated entries
where
(g,vs) = runState (traverseNSGraph (mapNode varb) gr) S.empty
-- Auxiliary monad function for rdfQuerySubs2.
-- This returns a state transformer Monad which in turn returns the
-- substituted node value based on the supplied query variable bindings.
-- The monad state is a set of labels which accumulates all those
-- variables seen for which no substitution was available.
mapNode :: RDFVarBinding -> RDFLabel -> State (S.Set RDFLabel) RDFLabel
mapNode varb lab =
case vbMap varb lab of
Just v -> return v
Nothing -> when (isQueryVar lab) (modify (S.insert lab)) >> return lab
------------------------------------------------------------
-- Simple lightweight query primitives
------------------------------------------------------------
--
-- [[[TODO: modify above code to use these for all graph queries]]]
-- |Test if a value satisfies all predicates in a list
--
allp :: [a->Bool] -> a -> Bool
allp ps a = and (flist ps a)
{-
allptest0 = allp [(>=1),(>=2),(>=3)] 0 -- False
allptest1 = allp [(>=1),(>=2),(>=3)] 1 -- False
allptest2 = allp [(>=1),(>=2),(>=3)] 2 -- False
allptest3 = allp [(>=1),(>=2),(>=3)] 3 -- True
allptest = and [not allptest0,not allptest1,not allptest2,allptest3]
-}
-- |Test if a value satisfies any predicate in a list
--
anyp :: [a->Bool] -> a -> Bool
anyp ps a = or (flist ps a)
{-
anyptest0 = anyp [(>=1),(>=2),(>=3)] 0 -- False
anyptest1 = anyp [(>=1),(>=2),(>=3)] 1 -- True
anyptest2 = anyp [(>=1),(>=2),(>=3)] 2 -- True
anyptest3 = anyp [(>=1),(>=2),(>=3)] 3 -- True
anyptest = and [not anyptest0,anyptest1,anyptest2,anyptest3]
-}
-- |Take a predicate on an
-- RDF statement and a graph, and returns all statements in the graph
-- satisfying that predicate.
--
-- Use combinations of these as follows:
--
-- * find all statements with given subject:
-- @rdfFindArcs (rdfSubjEq s)@
--
-- * find all statements with given property:
-- @rdfFindArcs (rdfPredEq p)@
--
-- * find all statements with given object:
-- @rdfFindArcs (rdfObjEq o)@
--
-- * find all statements matching conjunction of these conditions:
-- @rdfFindArcs ('allp' [...])@
--
-- * find all statements matching disjunction of these conditions:
-- @rdfFindArcs ('anyp' [...])@
--
-- Custom predicates can also be used.
--
rdfFindArcs :: (RDFTriple -> Bool) -> RDFGraph -> [RDFTriple]
rdfFindArcs p = S.toList . S.filter p . getArcs
-- |Test if statement has given subject
rdfSubjEq :: RDFLabel -> RDFTriple -> Bool
rdfSubjEq s = (s ==) . arcSubj
-- |Test if statement has given predicate
rdfPredEq :: RDFLabel -> RDFTriple -> Bool
rdfPredEq p = (p ==) . arcPred
-- |Test if statement has given object
rdfObjEq :: RDFLabel -> RDFTriple -> Bool
rdfObjEq o = (o ==) . arcObj
{-
-- |Find statements with given subject
rdfFindSubj :: RDFLabel -> RDFGraph -> [RDFTriple]
rdfFindSubj s = rdfFindArcs (rdfSubjEq s)
-- |Find statements with given predicate
rdfFindPred :: RDFLabel -> RDFGraph -> [RDFTriple]
rdfFindPred p = rdfFindArcs (rdfPredEq p)
-}
-- |Find values of given predicate for a given subject
rdfFindPredVal ::
RDFLabel -- ^ subject
-> RDFLabel -- ^ predicate
-> RDFGraph
-> [RDFLabel]
rdfFindPredVal s p = map arcObj . rdfFindArcs (allp [rdfSubjEq s,rdfPredEq p])
-- |Find integer values of a given predicate for a given subject
rdfFindPredInt ::
RDFLabel -- ^ subject
-> RDFLabel -- ^ predicate
-> RDFGraph -> [Integer]
rdfFindPredInt s p = mapMaybe getint . filter isint . pvs
where
pvs = rdfFindPredVal s p
isint = anyp
[ isDatatyped xsdInteger
, isDatatyped xsdNonNegInteger
]
getint = mapL2V mapXsdInteger . getLiteralText
-- |Find all subjects that match (subject, predicate, object) in the graph.
rdfFindValSubj ::
RDFLabel -- ^ predicate
-> RDFLabel -- ^ object
-> RDFGraph
-> [RDFLabel]
rdfFindValSubj p o = map arcSubj . rdfFindArcs (allp [rdfPredEq p,rdfObjEq o])
------------------------------------------------------------
-- List query
------------------------------------------------------------
-- |Return a list of nodes that comprise an rdf:collection value,
-- given the head element of the collection. If the list is
-- ill-formed then an arbitrary value is returned.
--
rdfFindList :: RDFGraph -> RDFLabel -> [RDFLabel]
rdfFindList gr hd = findhead $ rdfFindList gr findrest
where
findhead = headOr (const []) $
map (:) (rdfFindPredVal hd resRdfFirst gr)
findrest = headOr resRdfNil (rdfFindPredVal hd resRdfRest gr)
{-
findhead = headOr (const [])
[ (ob:) | Arc _ sb ob <- subgr, sb == resRdfFirst ]
findrest = headOr resRdfNil
[ ob | Arc _ sb ob <- subgr, sb == resRdfRest ]
subgr = filter ((==) hd . arcSubj) $ getArcs gr
-}
headOr = foldr const
-- headOr _ (x:_) = x
-- headOr x [] = x
------------------------------------------------------------
-- Interactive tests
------------------------------------------------------------
{-
s1 = Blank "s1"
p1 = Blank "p1"
o1 = Blank "o1"
s2 = Blank "s2"
p2 = Blank "p2"
o2 = Blank "o2"
qs1 = Var "s1"
qp1 = Var "p1"
qo1 = Var "o1"
qs2 = Var "s2"
qp2 = Var "p2"
qo2 = Var "o2"
qa1 = Arc qs1 qp1 qo1
qa2 = Arc qs2 qp2 qo2
qa3 = Arc qs2 p2 qo2
ta1 = Arc s1 p1 o1
ta2 = Arc s2 p2 o2
g1 = toRDFGraph [ta1,ta2]
g2 = toRDFGraph [qa3]
gb1 = getBinding matchQueryVariable qa1 ta1 -- ?s1=_:s1, ?p1=_:p1, ?o1=_:o1
gvs1 = qbMap (fromJust gb1) qs1 -- _:s1
gvp1 = qbMap (fromJust gb1) qp1 -- _:p1
gvo1 = qbMap (fromJust gb1) qo1 -- _:o1
gvs2 = qbMap (fromJust gb1) qs2 -- Nothing
gb3 = getBinding matchQueryVariable qa3 ta1 -- Nothing
gb4 = getBinding matchQueryVariable qa3 ta2 -- ?s2=_:s1, ?o2=_:o1
mqvs1 = matchQueryVariable qs2 s1
mqvp1 = matchQueryVariable p2 p1
-- rdfQueryFind
qfa = rdfQueryFind g2 g1
qp2a = rdfQueryPrim2 matchQueryVariable qa3 g1
-}
{- more tests
qb1a = rdfQueryBack1 [] [qa1] [ta1,ta2]
qb1 = rdfQueryBack1 [] [qa1,qa2] [ta1,ta2]
ql1 = length qb1
qv1 = map (qb1!!0!!0) [qs1,qp1,qo1,qs2,qp2,qo2]
qv2 = map (qb1!!0!!1) [qs1,qp1,qo1,qs2,qp2,qo2]
qv3 = map (qb1!!1!!0) [qs1,qp1,qo1,qs2,qp2,qo2]
qv4 = map (qb1!!1!!1) [qs1,qp1,qo1,qs2,qp2,qo2]
qv5 = map (qb1!!2!!0) [qs1,qp1,qo1,qs2,qp2,qo2]
qv6 = map (qb1!!2!!1) [qs1,qp1,qo1,qs2,qp2,qo2]
qv7 = map (qb1!!3!!0) [qs1,qp1,qo1,qs2,qp2,qo2]
qv8 = map (qb1!!3!!1) [qs1,qp1,qo1,qs2,qp2,qo2]
qb2 = rdfQueryBack2 matchQueryVariable [qa1,qa2] ta1
ql2 = length qb2
qv1 = map (qbMap $ head qb2) [qs1,qp1,qo1,qs2,qp2,qo2]
qv2 = map (qbMap $ head $ tail qb2) [qs1,qp1,qo1,qs2,qp2,qo2]
qb3 = rdfQueryBack2 matchQueryVariable [qa1,qa3] ta1
-}
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012, 2014, 2022 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/RDF/Ruleset.hs 0000644 0000000 0000000 00000052105 13543702315 015541 0 ustar 00 0000000 0000000 {-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
--------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : Ruleset
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012, 2014, 2016 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : CPP, OverloadedStrings
--
-- This module defines some datatypes and functions that are
-- used to define rules and rulesets over RDF graphs.
--
-- For the routines that accept a graph in N3 format, the following
-- namespaces are pre-defined for use by the graph:
-- @rdf:@ and @rdfs:@.
--
--------------------------------------------------------------------------------
module Swish.RDF.Ruleset
(
-- * Data types for RDF Ruleset
RDFFormula, RDFRule, RDFRuleMap
, RDFClosure, RDFRuleset, RDFRulesetMap
, nullRDFFormula
, GraphClosure(..), makeGraphClosureRule
, makeRDFGraphFromN3Builder
, makeRDFFormula
, makeRDFClosureRule
-- * Create rules using Notation3 statements
, makeN3ClosureRule
, makeN3ClosureSimpleRule
, makeN3ClosureModifyRule
, makeN3ClosureAllocatorRule
, makeNodeAllocTo
-- * Debugging
, graphClosureFwdApply, graphClosureBwdApply
)
where
import Swish.Namespace (Namespace, ScopedName)
import Swish.Namespace (makeNSScopedName, namespaceToBuilder)
import Swish.QName (LName)
import Swish.Rule (Formula(..), Rule(..), RuleMap)
import Swish.Rule (fwdCheckInference, nullSN)
import Swish.Ruleset (Ruleset(..), RulesetMap)
import Swish.GraphClass (Label(..), ArcSet, LDGraph(..))
import Swish.VarBinding (VarBindingModify(..))
import Swish.VarBinding (makeVarBinding, applyVarBinding, joinVarBindings, vbmCompose, varBindingId)
import Swish.RDF.Query
( rdfQueryFind
, rdfQueryBack, rdfQueryBackModify
, rdfQuerySubs
, rdfQuerySubsBlank
)
import Swish.RDF.Graph
( RDFLabel(..), RDFGraph, RDFArcSet
, makeBlank, newNodes
, merge, allLabels
, toRDFGraph)
import Swish.RDF.VarBinding (RDFVarBinding, RDFVarBindingModify)
import Swish.RDF.Parser.N3 (parseN3)
import Swish.RDF.Vocabulary (swishName, namespaceRDF, namespaceRDFS)
import Swish.Utils.ListHelpers (flist)
import Data.List (nub)
import Data.Maybe (fromMaybe)
#if (!defined(__GLASGOW_HASKELL__)) || (__GLASGOW_HASKELL__ < 710)
import Data.Monoid (Monoid(..))
#endif
import qualified Data.Set as S
import qualified Data.Text.Lazy.Builder as B
------------------------------------------------------------
-- Datatypes for RDF ruleset
------------------------------------------------------------
-- | A named formula expressed as a RDF Graph.
type RDFFormula = Formula RDFGraph
-- | A named inference rule expressed in RDF.
type RDFRule = Rule RDFGraph
-- | A map for 'RDFRule' rules.
type RDFRuleMap = RuleMap RDFGraph
-- | A 'GraphClosure' for RDF statements.
type RDFClosure = GraphClosure RDFLabel
-- | A 'Ruleset' for RDF.
type RDFRuleset = Ruleset RDFGraph
-- | A map for 'RDFRuleset'.
type RDFRulesetMap = RulesetMap RDFGraph
------------------------------------------------------------
-- Declare null RDF formula
------------------------------------------------------------
-- | The null RDF formula.
nullRDFFormula :: Formula RDFGraph
nullRDFFormula = Formula
{ formName = nullSN "nullRDFGraph"
, formExpr = mempty
}
------------------------------------------------------------
-- Datatype for graph closure rule
------------------------------------------------------------
-- |Datatype for constructing a graph closure rule
data GraphClosure lb = GraphClosure
{ nameGraphRule :: ScopedName -- ^ Name of rule for proof display
, ruleAnt :: ArcSet lb -- ^ Antecedent triples pattern
-- (may include variable nodes)
, ruleCon :: ArcSet lb -- ^ Consequent triples pattern
-- (may include variable nodes)
, ruleModify :: VarBindingModify lb lb
-- ^ Structure that defines additional
-- constraints and/or variable
-- bindings based on other matched
-- query variables. Matching the
-- antecedents. Use 'varBindingId' if
-- no additional variable constraints
-- or bindings are added beyond those
-- arising from graph queries.
}
-- | Equality is based on the closure rule, anrecedents and
-- consequents.
instance (Label lb) => Eq (GraphClosure lb) where
c1 == c2 = nameGraphRule c1 == nameGraphRule c2 &&
ruleAnt c1 == ruleAnt c2 &&
ruleCon c1 == ruleCon c2
instance Show (GraphClosure lb) where
show c = "GraphClosure " ++ show (nameGraphRule c)
------------------------------------------------------------
-- Define inference rule based on RDF graph closure rule
------------------------------------------------------------
-- |Define a value of type Rule based on an RDFClosure value.
makeGraphClosureRule :: GraphClosure RDFLabel -> Rule RDFGraph
makeGraphClosureRule grc = newrule
where
newrule = Rule
{ ruleName = nameGraphRule grc
, fwdApply = graphClosureFwdApply grc
, bwdApply = graphClosureBwdApply grc
, checkInference = fwdCheckInference newrule
}
-- | Forward chaining function based on RDF graph closure description
--
-- Note: antecedents here are presumed to share bnodes.
--
graphClosureFwdApply ::
GraphClosure RDFLabel
-> [RDFGraph]
-> [RDFGraph]
graphClosureFwdApply grc grs =
let gr = if null grs then mempty else foldl1 addGraphs grs
vars = queryFind (ruleAnt grc) gr
varm = vbmApply (ruleModify grc) vars
cons = querySubs varm (ruleCon grc)
in
{-
seq cons $
seq (trace "\ngraphClosureFwdApply") $
seq (traceShow "\nvars: " vars) $
seq (traceShow "\nvarm: " varm) $
seq (traceShow "\ncons: " cons) $
seq (trace "\n") $
-}
-- Return null list or single result graph that is the union
-- (not merge) of individual results:
if null cons then [] else [foldl1 addGraphs cons]
-- cons {- don't merge results -}
-- | Backward chaining function based on RDF graph closure description
graphClosureBwdApply :: GraphClosure RDFLabel -> RDFGraph -> [[RDFGraph]]
graphClosureBwdApply grc gr =
let vars = rdfQueryBackModify (ruleModify grc) $
queryBack (ruleCon grc) gr
-- This next function eliminates duplicate variable bindings.
-- It is strictly redundant, but comparing variable
-- bindings is much cheaper than comparing graphs.
-- I don't know if many duplicate graphs will be result
-- of exact duplicate variable bindings, so this may be
-- not very effective.
varn = map nub vars
in
-- The 'nub ante' below eliminates duplicate antecedent graphs,
-- based on graph matching, which tests for equivalence under
-- bnode renaming, with a view to reducing redundant arcs in
-- the merged antecedent graph, hence less to prove in
-- subsequent back-chaining steps.
--
-- Each antecedent is reduced to a single RDF graph, when
-- bwdApply specifies a list of expressions corresponding to
-- each antecedent.
[ [foldl1 merge (nub ante)]
| vs <- varn
, let ante = querySubsBlank vs (ruleAnt grc) ]
------------------------------------------------------------
-- RDF graph query and substitution support functions
------------------------------------------------------------
queryFind :: RDFArcSet -> RDFGraph -> [RDFVarBinding]
queryFind qas = rdfQueryFind (toRDFGraph qas)
queryBack :: RDFArcSet -> RDFGraph -> [[RDFVarBinding]]
queryBack qas = rdfQueryBack (toRDFGraph qas)
querySubs :: [RDFVarBinding] -> RDFArcSet -> [RDFGraph]
querySubs vars = rdfQuerySubs vars . toRDFGraph
querySubsBlank :: [RDFVarBinding] -> RDFArcSet -> [RDFGraph]
querySubsBlank vars = rdfQuerySubsBlank vars . toRDFGraph
------------------------------------------------------------
-- Method for creating an RDF formula value from N3 text
------------------------------------------------------------
mkPrefix :: Namespace -> B.Builder
mkPrefix = namespaceToBuilder
prefixRDF :: B.Builder
prefixRDF =
mconcat
[ mkPrefix namespaceRDF
, mkPrefix namespaceRDFS
]
-- |Helper function to parse a string containing Notation3
-- and return the corresponding RDFGraph value.
--
makeRDFGraphFromN3Builder :: B.Builder -> RDFGraph
makeRDFGraphFromN3Builder b =
let t = B.toLazyText (prefixRDF `mappend` b)
in case parseN3 t Nothing of
Left msg -> error msg
Right gr -> gr
-- |Create an RDF formula.
makeRDFFormula ::
Namespace -- ^ namespace to which the formula is allocated
-> LName -- ^ local name for the formula in the namespace
-> B.Builder -- ^ graph in Notation 3 format
-> RDFFormula
makeRDFFormula scope local gr =
Formula
{ formName = makeNSScopedName scope local
, formExpr = makeRDFGraphFromN3Builder gr
}
------------------------------------------------------------
-- Create an RDF closure rule from supplied graphs
------------------------------------------------------------
-- |Constructs an RDF graph closure rule. That is, a rule that
-- given some set of antecedent statements returns new statements
-- that may be added to the graph.
--
makeRDFClosureRule ::
ScopedName -- ^ scoped name for the new rule
-> [RDFGraph] -- ^ RDFGraphs that are the entecedent of the rule.
--
-- (Note: bnodes and variable names are assumed to be shared
-- by all the entecedent graphs supplied. /is this right?/)
-> RDFGraph -- ^ the consequent graph
-> RDFVarBindingModify -- ^ is a variable binding modifier value that may impose
-- additional conditions on the variable bindings that
-- can be used for this inference rule, or which may
-- cause new values to be allocated for unbound variables.
-- These modifiers allow for certain inference patterns
-- that are not captured by simple "closure rules", such
-- as the allocation of bnodes corresponding to literals,
-- and are an extension point for incorporating datatypes
-- into an inference process.
--
-- If no additional constraints or variable bindings are
-- to be applied, use value 'varBindingId'
--
-> RDFRule
makeRDFClosureRule sname antgrs congr vmod = makeGraphClosureRule
GraphClosure
{ nameGraphRule = sname
, ruleAnt = S.unions $ map getArcs antgrs
, ruleCon = getArcs congr
, ruleModify = vmod
}
------------------------------------------------------------
-- Methods to create an RDF closure rule from N3 input
------------------------------------------------------------
--
-- These functions are used internally by Swish to construct
-- rules from textual descriptions.
-- |Constructs an RDF graph closure rule. That is, a rule that
-- given some set of antecedent statements returns new statements
-- that may be added to the graph. This is the basis for
-- implementation of most of the inference rules given in the
-- RDF formal semantics document.
--
makeN3ClosureRule ::
Namespace -- ^ namespace to which the rule is allocated
-> LName -- ^ local name for the rule in the namespace
-> B.Builder
-- ^ the Notation3 representation
-- of the antecedent graph. (Note: multiple antecedents
-- can be handled by combining multiple graphs.)
-> B.Builder -- ^ the Notation3 representation of the consequent graph.
-> RDFVarBindingModify
-- ^ a variable binding modifier value that may impose
-- additional conditions on the variable bindings that
-- can be used for this inference rule, or which may
-- cause new values to be allocated for unbound variables.
-- These modifiers allow for certain inference patterns
-- that are not captured by simple closure rules, such
-- as the allocation of bnodes corresponding to literals,
-- and are an extension point for incorporating datatypes
-- into an inference process.
--
-- If no additional constraints or variable bindings are
-- to be applied, use a value of 'varBindingId', or use
-- 'makeN3ClosureSimpleRule'.
-> RDFRule
makeN3ClosureRule scope local ant con =
makeRDFClosureRule (makeNSScopedName scope local) [antgr] congr
where
antgr = makeRDFGraphFromN3Builder ant
congr = makeRDFGraphFromN3Builder con
-- |Construct a simple RDF graph closure rule without
-- additional node allocations or variable binding constraints.
--
makeN3ClosureSimpleRule ::
Namespace -- ^ namespace to which the rule is allocated
-> LName -- ^ local name for the rule in the namepace
-> B.Builder
-- ^ the Notation3 representation
-- of the antecedent graph. (Note: multiple antecedents
-- can be handled by combining multiple graphs.)
-> B.Builder -- ^ the Notation3 representation of the consequent graph.
-> RDFRule
makeN3ClosureSimpleRule scope local ant con =
makeN3ClosureRule scope local ant con varBindingId
-- |Constructs an RDF graph closure rule that incorporates
-- a variable binding filter and a variable binding modifier.
--
makeN3ClosureModifyRule ::
Namespace -- ^ namespace to which the rule is allocated
-> LName -- ^ local name for the rule in the given namespace
-> B.Builder -- ^ the Notation3 representation
-- of the antecedent graph. (Note: multiple antecedents
-- can be handled by combining multiple graphs.)
-> B.Builder -- ^ the Notation3 representation of the consequent graph.
-> RDFVarBindingModify
-- ^ a variable binding modifier value that may impose
-- additional conditions on the variable bindings that
-- can be used for this inference rule (@vflt@).
--
-- These modifiers allow for certain inference patterns
-- that are not captured by simple closure rules, such
-- as deductions that pertain only to certain kinds of
-- nodes in a graph.
-> RDFVarBindingModify
-- ^ a variable binding modifier that is applied to the
-- variable bindings obtained, typically to create some
-- additional variable bindings. This is applied before
-- the preceeding filter rule (@vflt@).
-> RDFRule
makeN3ClosureModifyRule scope local ant con vflt vmod =
makeN3ClosureRule scope local ant con modc
where
modc = fromMaybe varBindingId $ vbmCompose vmod vflt
{-
makeRDFClosureRule (ScopedName scope local) [antgr] congr modc
where
antgr = makeRDFGraphFromN3String ant
congr = makeRDFGraphFromN3String con
modc = case vbmCompose vmod vflt of
Just x -> x
Nothing -> varBindingId
-}
-- |Construct an RDF graph closure rule with a bnode allocator.
--
-- This function is rather like 'makeN3ClosureModifyRule', except that
-- the variable binding modifier is a function from the variables in
-- the variables and bnodes contained in the antecedent graph.
--
makeN3ClosureAllocatorRule ::
Namespace -- ^ namespace to which the rule is allocated
-> LName -- ^ local name for the rule in the given namespace
-> B.Builder -- ^ the Notation3 representation
-- of the antecedent graph. (Note: multiple antecedents
-- can be handled by combining multiple graphs.)
-> B.Builder -- ^ the Notation3 representation of the consequent graph.
-> RDFVarBindingModify
-- ^ variable binding modifier value that may impose
-- additional conditions on the variable bindings that
-- can be used for this inference rule (@vflt@).
-> ( [RDFLabel] -> RDFVarBindingModify )
-- ^ function applied to a list of nodes to yield a
-- variable binding modifier value.
--
-- The supplied parameter is applied to a list of all of
-- the variable nodes (including all blank nodes) in the
-- antecedent graph, and then composed with the @vflt@
-- value. This allows any node allocation
-- function to avoid allocating any blank nodes that
-- are already used in the antecedent graph.
-- (See 'makeNodeAllocTo').
-> RDFRule
makeN3ClosureAllocatorRule scope local ant con vflt aloc =
makeRDFClosureRule (makeNSScopedName scope local) [antgr] congr modc
where
antgr = makeRDFGraphFromN3Builder ant
congr = makeRDFGraphFromN3Builder con
vmod = aloc $ S.toList (allLabels labelIsVar antgr)
modc = fromMaybe varBindingId $ vbmCompose vmod vflt
------------------------------------------------------------
-- Query binding modifier for "allocated to" logic
------------------------------------------------------------
-- |This function defines a variable binding modifier that
-- allocates a new blank node for each value bound to
-- a query variable, and binds it to another variable
-- in each query binding.
--
-- This provides a single binding for query variables that would
-- otherwise be unbound by a query. For example, consider the
-- inference pattern:
--
-- > ?a hasUncle ?c => ?a hasFather ?b . ?b hasBrother ?c .
--
-- For a given @?a@ and @?c@, there is insufficient information
-- here to instantiate a value for variable @?b@. Using this
-- function as part of a graph instance closure rule allows
-- forward chaining to allocate a single bnode for each
-- occurrence of @?a@, so that given:
--
-- > Jimmy hasUncle Fred .
-- > Jimmy hasUncle Bob .
--
-- leads to exactly one bnode inference of:
--
-- > Jimmy hasFather _:f .
--
-- giving:
--
-- > Jimmy hasFather _:f .
-- > _:f hasBrother Fred .
-- > _:f hasBrother Bob .
--
-- rather than:
--
-- > Jimmy hasFather _:f1 .
-- > _:f1 hasBrother Fred .
-- > Jimmy hasFather _:f2 .
-- > _:f2 hasBrother Bob .
--
-- This form of constrained allocation of bnodes is also required for
-- some of the inference patterns described by the RDF formal semantics,
-- particularly those where bnodes are substituted for URIs or literals.
--
makeNodeAllocTo ::
RDFLabel -- ^ variable node to which a new blank node is bound
-> RDFLabel -- ^ variable which is bound in each query to a graph
-- node to which new blank nodes are allocated.
-> [RDFLabel]
-> RDFVarBindingModify
makeNodeAllocTo bindvar alocvar exbnode = VarBindingModify
{ vbmName = swishName "makeNodeAllocTo"
, vbmApply = applyNodeAllocTo bindvar alocvar exbnode
, vbmVocab = [alocvar,bindvar]
, vbmUsage = [[bindvar]]
}
-- Auxiliary function that performs the node allocation defined
-- by makeNodeAllocTo.
--
-- bindvar is a variable node to which a new blank node is bound
-- alocvar is a variable which is bound in each query to a graph
-- node to which new blank nodes are allocated.
-- exbnode is a list of existing blank nodes, to be avoided by
-- the new blank node allocator.
-- vars is a list of variable bindings to which new bnode
-- allocations for the indicated bindvar are to be added.
--
applyNodeAllocTo ::
RDFLabel -> RDFLabel -> [RDFLabel] -> [RDFVarBinding] -> [RDFVarBinding]
applyNodeAllocTo bindvar alocvar exbnode vars =
let
app = applyVarBinding
alocnodes = zip (nub $ flist (map app vars) alocvar)
(newNodes (makeBlank bindvar) exbnode)
newvb var = joinVarBindings
( makeVarBinding $ head
[ [(bindvar,b)] | (v,b) <- alocnodes, app var alocvar == v ] )
var
in
map newvb vars
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/RDF/VarBinding.hs 0000644 0000000 0000000 00000016253 13543702315 016145 0 ustar 00 0000000 0000000 {-# LANGUAGE OverloadedStrings #-}
--------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : VarBinding
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011, 2012 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : OverloadedStrings
--
-- This module instantiates the `VarBinding` types and methods for use
-- with RDF graph labels.
--
--------------------------------------------------------------------------------
-- See module RDFQueryTest for test cases.
module Swish.RDF.VarBinding
( RDFVarBinding
, RDFVarBindingModify, RDFOpenVarBindingModify, RDFOpenVarBindingModifyMap
, RDFVarBindingFilter
, nullRDFVarBinding
, rdfVarBindingUriRef, rdfVarBindingBlank
, rdfVarBindingLiteral
, rdfVarBindingUntypedLiteral, rdfVarBindingTypedLiteral
, rdfVarBindingXMLLiteral, rdfVarBindingDatatyped
, rdfVarBindingMemberProp
)
where
import Swish.Namespace (ScopedName)
import Swish.VarBinding (VarBinding(..), VarBindingModify(..), OpenVarBindingModify, VarBindingFilter(..))
import Swish.VarBinding (nullVarBinding, applyVarBinding, makeVarTestFilter)
import Swish.RDF.Graph
( RDFLabel(..)
, isLiteral, isUntypedLiteral, isTypedLiteral, isXMLLiteral
, isDatatyped, isMemberProp, isUri, isBlank
)
import Swish.RDF.Vocabulary (swishName)
import qualified Data.Map as M
------------------------------------------------------------
-- Types for RDF query variable bindings and modifiers
------------------------------------------------------------
-- |@RDFVarBinding@ is the specific type type of a variable
-- binding value used with RDF graph queries.
type RDFVarBinding = VarBinding RDFLabel RDFLabel
-- | maps no query variables.
nullRDFVarBinding :: RDFVarBinding
nullRDFVarBinding = nullVarBinding
-- |Define type of query binding modifier for RDF graph inference
type RDFVarBindingModify = VarBindingModify RDFLabel RDFLabel
-- |Open variable binding modifier that operates on RDFLabel values
--
type RDFOpenVarBindingModify = OpenVarBindingModify RDFLabel RDFLabel
-- |Define type for lookup map of open query binding modifiers
type RDFOpenVarBindingModifyMap = M.Map ScopedName RDFOpenVarBindingModify
-- |@RDFVarBindingFilter@ is a function type that tests to see if
-- a query binding satisfies some criterion, and is used to
-- create a variable binding modifier that simply filers
-- given variable bindings.
--
-- Queries often want to apply some kind of filter or condition
-- to the variable bindings that are processed. In inference rules,
-- it sometimes seems desirable to stipulate additional conditions on
-- the things that are matched.
--
-- This function type is used to perform such tests.
-- A number of simple implementations are included.
--
type RDFVarBindingFilter = VarBindingFilter RDFLabel RDFLabel
------------------------------------------------------------
-- Declare some query binding filters
------------------------------------------------------------
-- |This function generates a query binding filter that ensures
-- an indicated variable is bound to a URI reference.
rdfVarBindingUriRef :: RDFLabel -> RDFVarBindingFilter
rdfVarBindingUriRef =
makeVarTestFilter (swishName "rdfVarBindingUriRef") isUri
-- |This function generates a query binding filter that ensures
-- an indicated variable is bound to a blank node.
rdfVarBindingBlank :: RDFLabel -> RDFVarBindingFilter
rdfVarBindingBlank =
makeVarTestFilter (swishName "rdfVarBindingBlank") isBlank
-- |This function generates a query binding filter that ensures
-- an indicated variable is bound to a literal value.
rdfVarBindingLiteral :: RDFLabel -> RDFVarBindingFilter
rdfVarBindingLiteral =
makeVarTestFilter (swishName "rdfVarBindingLiteral") isLiteral
-- |This function generates a query binding filter that ensures
-- an indicated variable is bound to an untyped literal value.
rdfVarBindingUntypedLiteral :: RDFLabel -> RDFVarBindingFilter
rdfVarBindingUntypedLiteral =
makeVarTestFilter (swishName "rdfVarBindingUntypedLiteral") isUntypedLiteral
-- |This function generates a query binding filter that ensures
-- an indicated variable is bound to a typed literal value.
rdfVarBindingTypedLiteral :: RDFLabel -> RDFVarBindingFilter
rdfVarBindingTypedLiteral =
makeVarTestFilter (swishName "rdfVarBindingTypedLiteral") isTypedLiteral
-- |This function generates a query binding filter that ensures
-- an indicated variable is bound to an XML literal value.
rdfVarBindingXMLLiteral :: RDFLabel -> RDFVarBindingFilter
rdfVarBindingXMLLiteral =
makeVarTestFilter (swishName "rdfVarBindingXMLLiteral") isXMLLiteral
-- |This function generates a query binding filter that ensures
-- an indicated variable is bound to container membership property.
rdfVarBindingMemberProp :: RDFLabel -> RDFVarBindingFilter
rdfVarBindingMemberProp =
makeVarTestFilter (swishName "rdfVarBindingMemberProp") isMemberProp
-- |This function generates a query binding filter that ensures
-- an indicated variable is bound to a literal value with a
-- datatype whose URI is bound to another node
--
rdfVarBindingDatatyped ::
RDFLabel -- ^ variable bound to the required datatype.
-> RDFLabel -- ^ variable bound to the literal node to be tested.
-> RDFVarBindingFilter
rdfVarBindingDatatyped dvar lvar = VarBindingFilter
{ vbfName = swishName "rdfVarBindingDatatyped"
, vbfVocab = [dvar,lvar]
, vbfTest = \vb -> testDatatyped vb dvar lvar
}
testDatatyped :: RDFVarBinding -> RDFLabel -> RDFLabel -> Bool
testDatatyped vb dvar lvar = and
[ isUri dtype
, isDatatyped dqnam $ applyVarBinding vb lvar
]
where
dtype = applyVarBinding vb dvar
-- NOTE: dqnam is not evaluated unless (isUri dtype)
-- but add in a _ handler to appease -Wall
-- dqnam = case dtype of { (Res x) -> x }
dqnam = case dtype of
Res x -> x
_ -> error $ "dqnam should not be evaluated with " ++ show dtype
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin,
-- 2011, 2012 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/RDF/Vocabulary.hs 0000644 0000000 0000000 00000025611 14220136201 016213 0 ustar 00 0000000 0000000 {-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
--------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : Vocabulary
-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011, 2014, 2021, 2022 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : CPP, OverloadedStrings
--
-- This module defines some commonly used vocabulary terms,
-- using the 'Namespace' and 'ScopedName' data types. Additional vocabularies
-- are available in the set of @Swish.RDF.Vocabulary.*@ modules, parts of
-- which are re-exported by this module
--
--------------------------------------------------------------------------------
module Swish.RDF.Vocabulary
(
-- * Namespaces
namespaceRDFD
, namespaceXsdType
, namespaceMATH
, namespaceLOG
, namespaceDAML
, namespaceDefault
, namespaceSwish
-- ** RDF rules
-- | The namespaces refer to RDF rules and axioms.
, scopeRDF
, scopeRDFS
, scopeRDFD
-- * Language tags
--
-- | Support for language tags that follow RFC 3066.
--
-- This replaces the use of @ScopedName@ and @langName@, @langTag@,
-- and @isLang@ in versions prior to @0.7.0.0@.
--
, LanguageTag
, toLangTag
, fromLangTag
, isBaseLang
-- * Miscellaneous routines
, swishName
, rdfdGeneralRestriction
, rdfdOnProperties, rdfdConstraint, rdfdMaxCardinality
, logImplies
, defaultBase
-- * Re-exported modules
, module Swish.RDF.Vocabulary.RDF
, module Swish.RDF.Vocabulary.OWL
, module Swish.RDF.Vocabulary.XSD
)
where
import Swish.Namespace (Namespace, ScopedName, makeNamespace, makeNSScopedName)
import Swish.QName (LName, getLName)
import Swish.RDF.Vocabulary.RDF
import Swish.RDF.Vocabulary.OWL
import Swish.RDF.Vocabulary.XSD
import Control.Monad (guard)
import Data.Char (isDigit, isAsciiLower)
import Data.List (isPrefixOf)
import Data.List.NonEmpty (NonEmpty(..))
#if (!defined(__GLASGOW_HASKELL__)) || (__GLASGOW_HASKELL__ < 710)
import Data.Monoid (mappend, mconcat)
#endif
import Data.Maybe (fromJust, fromMaybe)
import Data.String (IsString(..))
import Network.URI (URI, parseURI)
import qualified Data.List.NonEmpty as NE
import qualified Data.Text as T
------------------------------------------------------------
-- Define some common namespace values
------------------------------------------------------------
toNS :: T.Text -> T.Text -> Namespace
toNS p utxt =
let ustr = T.unpack utxt
uri = fromMaybe (error ("Unable to convert " ++ ustr ++ " to a URI")) $
parseURI ustr
in makeNamespace (Just p) uri
toNSU :: T.Text -> URI -> Namespace
toNSU p = makeNamespace (Just p)
-- | Create a namespace for the datatype family schema used by Swish.
namespaceXsdType ::
LName -- ^ local name
-> Namespace
-- ^ Namespace has prefix @xsd_lbl@ and
-- URI of @http:\/\/id.ninebynine.org\/2003\/XMLSchema\/lbl#@.
namespaceXsdType lbl =
let dtn = getLName lbl
in toNS ("xsd_" `mappend` dtn)
(mconcat ["http://id.ninebynine.org/2003/XMLSchema/", dtn, "#"])
-- | Maps @rdfd@ to @http:\/\/id.ninebynine.org\/2003\/rdfext\/rdfd#@.
namespaceRDFD :: Namespace
namespaceRDFD = toNSU "rdfd" namespaceRDFDURI
-- | Maps @math@ to .
namespaceMATH :: Namespace
namespaceMATH = toNS "math" "http://www.w3.org/2000/10/swap/math#"
-- | Maps @log@ to .
namespaceLOG :: Namespace
namespaceLOG = toNSU "log" namespaceLOGURI
-- | Maps @daml@ to .
namespaceDAML :: Namespace
namespaceDAML = toNS "daml" "http://www.daml.org/2000/10/daml-ont#"
-- | Maps @swish@ to @http:\/\/id.ninebynine.org\/2003\/Swish\/@.
namespaceSwish :: Namespace
namespaceSwish = toNSU "swish" namespaceSwishURI
-- | Maps @default@ to @http:\/\/id.ninebynine.org\/default\/@.
namespaceDefault :: Namespace
namespaceDefault = toNSU "default" namespaceDefaultURI
tU :: String -> URI
tU = fromMaybe (error "Internal error processing namespace URI") . parseURI
namespaceRDFDURI,
namespaceLOGURI,
namespaceSwishURI,
namespaceDefaultURI :: URI
namespaceRDFDURI = tU "http://id.ninebynine.org/2003/rdfext/rdfd#"
namespaceLOGURI = tU "http://www.w3.org/2000/10/swap/log#"
namespaceSwishURI = tU "http://id.ninebynine.org/2003/Swish/"
namespaceDefaultURI = tU "http://id.ninebynine.org/default/"
-- | Convert a local name to a scoped name in the @swish@ namespace (`namespaceSwish`).
swishName :: LName -> ScopedName
swishName = makeNSScopedName namespaceSwish
-----------------------------------------------------------
-- Language tags
------------------------------------------------------------
--
-- Note: simple language tag URIs may be abbreviated as lang:tag,
-- but if the tag contains a hyphen, this would not be valid QName
-- form in Notation3, even though it is a valid QName component.
-- Fortunately, they do not currently need to appear in Notation3 as
-- distinct labels (but future developments may change that).
-- | Represent the language tag for a literal string, following
-- RFC 3066 .
--
-- Use 'toLangTag' to create a tag and 'fromLangTag' to
-- convert back. The case is preserved for the tag, although
-- comparison (both the 'Eq' instance and 'compareLangTag')
-- is done using the lower-case form of the tags.
--
-- As an example:
--
-- > Prelude> :set prompt "swish> "
-- > swish> :set -XOverloadedStrings
-- > swish> :m + Swish.RDF.Vocabulary
-- > swish> let en = "en" :: LanguageTag
-- > swish> let us = "en-us" :: LanguageTag
-- > swish> let gb = "en-GB" :: LanguageTag
-- > swish> gb
-- > en-GB
-- > swish> gb == "en-gb"
-- > True
-- > swish> en == us
-- > False
-- > swish> en `isBaseLang` us
-- > True
-- > swish> us `isBaseLang` en
-- > False
-- > swish> us `isBaseLang` gb
-- > False
--
data LanguageTag =
LanguageTag T.Text (NonEmpty T.Text)
-- store full value, then the tags
instance Show LanguageTag where
show = T.unpack . fromLangTag
-- | The 'IsString' instance is not total since it will fail
-- given a syntactically-invalid language tag.
instance IsString LanguageTag where
fromString = fromJust . toLangTag . T.pack
-- | The equality test matches on the full definition, so
-- @en-GB@ does not match @en@. See also 'isBaseLang'.
instance Eq LanguageTag where
LanguageTag _ t1 == LanguageTag _ t2 = t1 == t2
instance Ord LanguageTag where
LanguageTag _ t1 `compare` LanguageTag _ t2 = t1 `compare` t2
-- | Create a 'LanguageTag' element from the label.
--
-- Valid tags follow the ABNF from RCF 3066, which is
--
-- > Language-Tag = Primary-subtag *( "-" Subtag )
-- > Primary-subtag = 1*8ALPHA
-- > Subtag = 1*8(ALPHA / DIGIT)
--
-- There are no checks that the primary or secondary sub tag
-- values are defined in any standard, such as ISO 639,
-- or obey any other syntactical restriction than given above.
--
toLangTag :: T.Text -> Maybe LanguageTag
toLangTag lbl = do
let tag = T.toLower lbl
toks = T.split (== '-') tag
guard (all (\s -> let l = T.length s in l > 0 && l < 9) toks)
-- T.split can't return [] but the compiler doesn't know this
case toks of
primtag : subtags -> do
guard (T.all isAsciiLower primtag && all (T.all (\c -> isAsciiLower c || isDigit c)) subtags)
pure $ LanguageTag lbl (NE.fromList toks)
[] -> Nothing
-- | Convert a language tag back into text form.
fromLangTag :: LanguageTag -> T.Text
fromLangTag (LanguageTag f _) = f
-- | Compare language tags using the Language-range specification
-- in section 2.5 of RFC 3066.
--
-- 'True' is returned if the comparison tag is the same as, or
-- matches a prefix of, the base tag (where the match must be
-- over complete sub tags).
--
-- Note that
--
-- > l1 `isBaseLang` l2 == l2 `isBaseLang` l1
--
-- only when
--
-- > l1 == l2
--
isBaseLang ::
LanguageTag -- ^ base language
-> LanguageTag -- ^ comparison language
-> Bool
isBaseLang (LanguageTag _ (a :| as))
(LanguageTag _ (b :| bs))
| a == b = as `isPrefixOf` bs
| otherwise = False
------------------------------------------------------------
-- Define namespaces for RDF rules, axioms, etc
------------------------------------------------------------
-- | Maps @rs_rdf@ to @http:\/\/id.ninebynine.org\/2003\/Ruleset\/rdf#@.
scopeRDF :: Namespace
scopeRDF = toNS "rs_rdf" "http://id.ninebynine.org/2003/Ruleset/rdf#"
-- | Maps @rs_rdfs@ to @http:\/\/id.ninebynine.org\/2003\/Ruleset\/rdfs#@.
scopeRDFS :: Namespace
scopeRDFS = toNS "rs_rdfs" "http://id.ninebynine.org/2003/Ruleset/rdfs#"
-- | Maps @rs_rdfd@ to @http:\/\/id.ninebynine.org\/2003\/Ruleset\/rdfd#@.
scopeRDFD :: Namespace
scopeRDFD = toNS "rs_rdfd" "http://id.ninebynine.org/2003/Ruleset/rdfd#"
------------------------------------------------------------
-- Define some common vocabulary terms
------------------------------------------------------------
toRDFD :: LName -> ScopedName
toRDFD = makeNSScopedName namespaceRDFD
-- | @rdfd:GeneralRestriction@.
rdfdGeneralRestriction :: ScopedName
rdfdGeneralRestriction = toRDFD "GeneralRestriction"
-- | @rdfd:onProperties@.
rdfdOnProperties :: ScopedName
rdfdOnProperties = toRDFD "onProperties"
-- | @rdfd:constraint@.
rdfdConstraint :: ScopedName
rdfdConstraint = toRDFD "constraint"
-- | @rdfd:maxCardinality@.
rdfdMaxCardinality :: ScopedName
rdfdMaxCardinality = toRDFD "maxCardinality"
-- | @log:implies@.
logImplies :: ScopedName
logImplies = makeNSScopedName namespaceLOG "implies"
-- | @default:base@.
defaultBase :: ScopedName
defaultBase = makeNSScopedName namespaceDefault "base"
--------------------------------------------------------------------------------
--
-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011, 2014, 2021, 2022 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/RDF/Vocabulary/DublinCore.hs 0000644 0000000 0000000 00000064645 13543702315 020267 0 ustar 00 0000000 0000000 {-# LANGUAGE OverloadedStrings #-}
--------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : Swish.RDF.Vocabulary.DublinCore
-- Copyright : (c) 2011 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : OverloadedStrings
--
-- This module defines some commonly used vocabulary terms from the Dublin Core
-- vocabularies ().
--
--------------------------------------------------------------------------------
module Swish.RDF.Vocabulary.DublinCore
(
namespaceDCTERMS
, namespaceDCELEM
, namespaceDCAM
, namespaceDCTYPE
-- * Classes
-- | See the \"Classes\" section at .
, dctAgent
, dctAgentClass
, dctBibliographicResource
, dctFileFormat
, dctFrequency
, dctJurisdiction
, dctLicenseDocument
, dctLinguisticSystem
, dctLocation
, dctLocationPeriodOrJurisdiction
, dctMediaType
, dctMediaTypeOrExtent
, dctMethodOfAccrual
, dctMethodOfInstruction
, dctPeriodOfTime
, dctPhysicalMedium
, dctPhysicalResource
, dctPolicy
, dctProvenanceStatement
, dctRightsStatement
, dctSizeOrDuration
, dctStandard
-- * Properties
-- | See the \"Properties\" section at .
, dctabstract
, dctaccessRights
, dctaccrualMethod
, dctaccrualPeriodicity
, dctaccrualPolicy
, dctalternative
, dctaudience
, dctavailable
, dctbibliographicCitation
, dctconformsTo
, dctcontributor
, dctcoverage
, dctcreated
, dctcreator
, dctdate
, dctdateAccepted
, dctdateCopyrighted
, dctdateSubmitted
, dctdescription
, dcteducationLevel
, dctextent
, dctformat
, dcthasFormat
, dcthasPart
, dcthasVersion
, dctidentifier
, dctinstructionalMethod
, dctisFormatOf
, dctisPartOf
, dctisReferencedBy
, dctisReplacedBy
, dctisRequiredBy
, dctissued
, dctisVersionOf
, dctlanguage
, dctlicense
, dctmediator
, dctmedium
, dctmodified
, dctprovenance
, dctpublisher
, dctreferences
, dctrelation
, dctreplaces
, dctrequires
, dctrights
, dctrightsHolder
, dctsource
, dctspatial
, dctsubject
, dcttableOfContents
, dcttemporal
, dcttitle
, dcttype
, dctvalid
-- * Legacy Properties
--
-- | The following properties are from the legacy /elements/ vocabulary
-- (@http:\/\/purl.org\/dc\/elements\/1.1\/contributor\/@). See
-- .
, dcelemcontributor
, dcelemcoverage
, dcelemcreator
, dcelemdate
, dcelemdescription
, dcelemformat
, dcelemidentifier
, dcelemlanguage
, dcelempublisher
, dcelemrelation
, dcelemrights
, dcelemsource
, dcelemsubject
, dcelemtitle
, dcelemtype
-- * Encoding
-- | See the \"Vocabulary Encoding Schemes\" section at .
, dctLCSH
, dctMESH
, dctDDC
, dctLCC
, dctUDC
, dctDCMIType
, dctIMT
, dctTGN
, dctNLM
-- * Datatypes
-- | See the \"Syntax Encoding Schemes\" section at .
, dctBox
, dctISO3166
, dctISO639_2
, dctISO639_3
, dctPeriod
, dctPoint
, dctRFC1766
, dctRFC3066
, dctRFC4646
, dctRFC5646
, dctURI
, dctW3CDTF
-- * Types
-- | See the \"DCMI Type Vocabulary\" section at .
, dctypeCollection
, dctypeDataset
, dctypeEvent
, dctypeImage
, dctypeInteractiveResource
, dctypeService
, dctypeSoftware
, dctypeSound
, dctypeText
, dctypePhysicalObject
, dctypeStillImage
, dctypeMovingImage
-- * DCMI Abstract Model
--
-- | Terms from the DCMI Abstract Model ().
, dcammemberOf
, dcamVocabularyEncodingScheme
) where
import Swish.Namespace (Namespace, makeNamespace, ScopedName, makeNSScopedName)
import Swish.QName (LName)
import Data.Maybe (fromMaybe)
import Network.URI (parseURI)
import qualified Data.Text as T
------------------------------------------------------------
-- Namespace
------------------------------------------------------------
toNS :: T.Text -> String -> Namespace
toNS p = makeNamespace (Just p) . fromMaybe (error "Internal error processing DC URI") . parseURI
-- | Maps @dcterms@ to .
namespaceDCTERMS :: Namespace
namespaceDCTERMS = toNS "dcterms" "http://purl.org/dc/terms/"
-- | Maps @dcelem@ to the legacy namespace .
namespaceDCELEM :: Namespace
namespaceDCELEM = toNS "dcelem" "http://purl.org/dc/elements/1.1/"
-- | Maps @dcam@ to .
namespaceDCAM :: Namespace
namespaceDCAM = toNS "dcam" "http://purl.org/dc/dcam/"
-- | Maps @dctype@ to .
namespaceDCTYPE :: Namespace
namespaceDCTYPE = toNS "dctype" "http://purl.org/dc/dcmitype/"
------------------------------------------------------------
-- Terms
------------------------------------------------------------
toDCT, toDCE, toDCAM, toDCTYPE :: LName -> ScopedName
toDCT = makeNSScopedName namespaceDCTERMS
toDCE = makeNSScopedName namespaceDCELEM
toDCAM = makeNSScopedName namespaceDCAM
toDCTYPE = makeNSScopedName namespaceDCTYPE
-- Classes
-- | @dcterms:Agent@ from .
dctAgent :: ScopedName
dctAgent = toDCT "Agent"
-- | @dcterms:AgentClass@ from .
dctAgentClass :: ScopedName
dctAgentClass = toDCT "AgentClass"
-- | @dcterms:BibliographicResource@ from .
dctBibliographicResource :: ScopedName
dctBibliographicResource = toDCT "BibliographicResource"
-- | @dcterms:FileFormat@ from .
dctFileFormat :: ScopedName
dctFileFormat = toDCT "FileFormat"
-- | @dcterms:Frequency@ from .
dctFrequency :: ScopedName
dctFrequency = toDCT "Frequency"
-- | @dcterms:Jurisdiction@ from .
dctJurisdiction :: ScopedName
dctJurisdiction = toDCT "Jurisdiction"
-- | @dcterms:LicenseDocument@ from .
dctLicenseDocument :: ScopedName
dctLicenseDocument = toDCT "LicenseDocument"
-- | @dcterms:LinguisticSystem@ from .
dctLinguisticSystem :: ScopedName
dctLinguisticSystem = toDCT "LinguisticSystem"
-- | @dcterms:Location@ from .
dctLocation :: ScopedName
dctLocation = toDCT "Location"
-- | @dcterms:LocationPeriodOrJurisdiction@ from .
dctLocationPeriodOrJurisdiction :: ScopedName
dctLocationPeriodOrJurisdiction = toDCT "LocationPeriodOrJurisdiction"
-- | @dcterms:MediaType@ from .
dctMediaType :: ScopedName
dctMediaType = toDCT "MediaType"
-- | @dcterms:MediaTypeOrExtent@ from .
dctMediaTypeOrExtent :: ScopedName
dctMediaTypeOrExtent = toDCT "MediaTypeOrExtent"
-- | @dcterms:MethodOfAccrual@ from .
dctMethodOfAccrual :: ScopedName
dctMethodOfAccrual = toDCT "MethodOfAccrual"
-- | @dcterms:MethodOfInstruction@ from .
dctMethodOfInstruction :: ScopedName
dctMethodOfInstruction = toDCT "MethodOfInstruction"
-- | @dcterms:PeriodOfTime@ from .
dctPeriodOfTime :: ScopedName
dctPeriodOfTime = toDCT "PeriodOfTime"
-- | @dcterms:PhysicalMedium@ from .
dctPhysicalMedium :: ScopedName
dctPhysicalMedium = toDCT "PhysicalMedium"
-- | @dcterms:PhysicalResource@ from .
dctPhysicalResource :: ScopedName
dctPhysicalResource = toDCT "PhysicalResource"
-- | @dcterms:Policy@ from .
dctPolicy :: ScopedName
dctPolicy = toDCT "Policy"
-- | @dcterms:ProvenanceStatement@ from .
dctProvenanceStatement :: ScopedName
dctProvenanceStatement = toDCT "ProvenanceStatement"
-- | @dcterms:RightsStatement@ from .
dctRightsStatement :: ScopedName
dctRightsStatement = toDCT "RightsStatement"
-- | @dcterms:SizeOrDuration@ from .
dctSizeOrDuration :: ScopedName
dctSizeOrDuration = toDCT "SizeOrDuration"
-- | @dcterms:Standard@ from .
dctStandard :: ScopedName
dctStandard = toDCT "Standard"
-- Properties
-- | @dcterms:abstract@ from .
dctabstract :: ScopedName
dctabstract = toDCT "abstract"
-- | @dcterms:accessRights@ from .
dctaccessRights :: ScopedName
dctaccessRights = toDCT "accessRights"
-- | @dcterms:accrualMethod@ from .
dctaccrualMethod :: ScopedName
dctaccrualMethod = toDCT "accrualMethod"
-- | @dcterms:accrualPeriodicity@ from .
dctaccrualPeriodicity :: ScopedName
dctaccrualPeriodicity = toDCT "accrualPeriodicity"
-- | @dcterms:accrualPolicy@ from .
dctaccrualPolicy :: ScopedName
dctaccrualPolicy = toDCT "accrualPolicy"
-- | @dcterms:alternative@ from .
dctalternative :: ScopedName
dctalternative = toDCT "alternative"
-- | @dcterms:audience@ from .
dctaudience :: ScopedName
dctaudience = toDCT "audience"
-- | @dcterms:available@ from .
dctavailable :: ScopedName
dctavailable = toDCT "available"
-- | @dcterms:bibliographicCitation@ from .
dctbibliographicCitation :: ScopedName
dctbibliographicCitation = toDCT "bibliographicCitation"
-- | @dcterms:conformsTo@ from .
dctconformsTo :: ScopedName
dctconformsTo = toDCT "conformsTo"
-- | @dcterms:contributor@ from .
dctcontributor :: ScopedName
dctcontributor = toDCT "contributor"
-- | @dcterms:coverage@ from .
dctcoverage :: ScopedName
dctcoverage = toDCT "coverage"
-- | @dcterms:created@ from .
dctcreated :: ScopedName
dctcreated = toDCT "created"
-- | @dcterms:creator@ from .
dctcreator :: ScopedName
dctcreator = toDCT "creator"
-- | @dcterms:date@ from .
dctdate :: ScopedName
dctdate = toDCT "date"
-- | @dcterms:dateAccepted@ from .
dctdateAccepted :: ScopedName
dctdateAccepted = toDCT "dateAccepted"
-- | @dcterms:dateCopyrighted@ from .
dctdateCopyrighted :: ScopedName
dctdateCopyrighted = toDCT "dateCopyrighted"
-- | @dcterms:dateSubmitted@ from .
dctdateSubmitted :: ScopedName
dctdateSubmitted = toDCT "dateSubmitted"
-- | @dcterms:description@ from .
dctdescription :: ScopedName
dctdescription = toDCT "description"
-- | @dcterms:educationLevel@ from .
dcteducationLevel :: ScopedName
dcteducationLevel = toDCT "educationLevel"
-- | @dcterms:extent@ from .
dctextent :: ScopedName
dctextent = toDCT "extent"
-- | @dcterms:format@ from .
dctformat :: ScopedName
dctformat = toDCT "format"
-- | @dcterms:hasFormat@ from .
dcthasFormat :: ScopedName
dcthasFormat = toDCT "hasFormat"
-- | @dcterms:hasPart@ from .
dcthasPart :: ScopedName
dcthasPart = toDCT "hasPart"
-- | @dcterms:hasVersion@ from .
dcthasVersion :: ScopedName
dcthasVersion = toDCT "hasVersion"
-- | @dcterms:identifier@ from .
dctidentifier :: ScopedName
dctidentifier = toDCT "identifier"
-- | @dcterms:instructionalMethod@ from .
dctinstructionalMethod :: ScopedName
dctinstructionalMethod = toDCT "instructionalMethod"
-- | @dcterms:isFormatOf@ from .
dctisFormatOf :: ScopedName
dctisFormatOf = toDCT "isFormatOf"
-- | @dcterms:isPartOf@ from .
dctisPartOf :: ScopedName
dctisPartOf = toDCT "isPartOf"
-- | @dcterms:isReferencedBy@ from .
dctisReferencedBy :: ScopedName
dctisReferencedBy = toDCT "isReferencedBy"
-- | @dcterms:isReplacedBy@ from .
dctisReplacedBy :: ScopedName
dctisReplacedBy = toDCT "isReplacedBy"
-- | @dcterms:isRequiredBy@ from .
dctisRequiredBy :: ScopedName
dctisRequiredBy = toDCT "isRequiredBy"
-- | @dcterms:issued@ from .
dctissued :: ScopedName
dctissued = toDCT "issued"
-- | @dcterms:isVersionOf@ from .
dctisVersionOf :: ScopedName
dctisVersionOf = toDCT "isVersionOf"
-- | @dcterms:language@ from .
dctlanguage :: ScopedName
dctlanguage = toDCT "language"
-- | @dcterms:license@ from .
dctlicense :: ScopedName
dctlicense = toDCT "license"
-- | @dcterms:mediator@ from .
dctmediator :: ScopedName
dctmediator = toDCT "mediator"
-- | @dcterms:medium@ from .
dctmedium :: ScopedName
dctmedium = toDCT "medium"
-- | @dcterms:modified@ from .
dctmodified :: ScopedName
dctmodified = toDCT "modified"
-- | @dcterms:provenance@ from .
dctprovenance :: ScopedName
dctprovenance = toDCT "provenance"
-- | @dcterms:publisher@ from .
dctpublisher :: ScopedName
dctpublisher = toDCT "publisher"
-- | @dcterms:references@ from .
dctreferences :: ScopedName
dctreferences = toDCT "references"
-- | @dcterms:relation@ from .
dctrelation :: ScopedName
dctrelation = toDCT "relation"
-- | @dcterms:replaces@ from .
dctreplaces :: ScopedName
dctreplaces = toDCT "replaces"
-- | @dcterms:requires@ from .
dctrequires :: ScopedName
dctrequires = toDCT "requires"
-- | @dcterms:rights@ from .
dctrights :: ScopedName
dctrights = toDCT "rights"
-- | @dcterms:rightsHolder@ from .
dctrightsHolder :: ScopedName
dctrightsHolder = toDCT "rightsHolder"
-- | @dcterms:source@ from .
dctsource :: ScopedName
dctsource = toDCT "source"
-- | @dcterms:spatial@ from .
dctspatial :: ScopedName
dctspatial = toDCT "spatial"
-- | @dcterms:subject@ from .
dctsubject :: ScopedName
dctsubject = toDCT "subject"
-- | @dcterms:tableOfContents@ from .
dcttableOfContents :: ScopedName
dcttableOfContents = toDCT "tableOfContents"
-- | @dcterms:temporal@ from .
dcttemporal :: ScopedName
dcttemporal = toDCT "temporal"
-- | @dcterms:title@ from .
dcttitle :: ScopedName
dcttitle = toDCT "title"
-- | @dcterms:type@ from .
dcttype :: ScopedName
dcttype = toDCT "type"
-- | @dcterms:valid@ from .
dctvalid :: ScopedName
dctvalid = toDCT "valid"
-- legacy elements vocabulary: properties
-- | @dcelem:contributor@ from .
dcelemcontributor :: ScopedName
dcelemcontributor = toDCE "contributor"
-- | @dcelem:coverage@ from .
dcelemcoverage :: ScopedName
dcelemcoverage = toDCE "coverage"
-- | @dcelem:creator@ from .
dcelemcreator :: ScopedName
dcelemcreator = toDCE "creator"
-- | @dcelem:date@ from .
dcelemdate :: ScopedName
dcelemdate = toDCE "date"
-- | @dcelem:description@ from .
dcelemdescription :: ScopedName
dcelemdescription = toDCE "description"
-- | @dcelem:format@ from .
dcelemformat :: ScopedName
dcelemformat = toDCE "format"
-- | @dcelem:identifier@ from .
dcelemidentifier :: ScopedName
dcelemidentifier = toDCE "identifier"
-- | @dcelem:language@ from .
dcelemlanguage :: ScopedName
dcelemlanguage = toDCE "language"
-- | @dcelem:publisher@ from .
dcelempublisher :: ScopedName
dcelempublisher = toDCE "publisher"
-- | @dcelem:relation@ from .
dcelemrelation :: ScopedName
dcelemrelation = toDCE "relation"
-- | @dcelem:rights@ from .
dcelemrights :: ScopedName
dcelemrights = toDCE "rights"
-- | @dcelem:source@ from .
dcelemsource :: ScopedName
dcelemsource = toDCE "source"
-- | @dcelem:subject@ from .
dcelemsubject :: ScopedName
dcelemsubject = toDCE "subject"
-- | @dcelem:title@ from .
dcelemtitle :: ScopedName
dcelemtitle = toDCE "title"
-- | @dcelem:type@ from .
dcelemtype :: ScopedName
dcelemtype = toDCE "type"
-- Datatypes
-- | @dcterms:Box@ from .
dctBox :: ScopedName
dctBox = toDCT "Box"
-- | @dcterms:ISO3166@ from .
dctISO3166 :: ScopedName
dctISO3166 = toDCT "ISO3166"
-- | @dcterms:ISO639-2@ from .
dctISO639_2 :: ScopedName
dctISO639_2 = toDCT "ISO639-2"
-- | @dcterms:ISO639-3@ from .
dctISO639_3 :: ScopedName
dctISO639_3 = toDCT "ISO639-3"
-- | @dcterms:Period@ from .
dctPeriod :: ScopedName
dctPeriod = toDCT "Period"
-- | @dcterms:Point@ from .
dctPoint :: ScopedName
dctPoint = toDCT "Point"
-- | @dcterms:RFC1766@ from .
dctRFC1766 :: ScopedName
dctRFC1766 = toDCT "RFC1766"
-- | @dcterms:RFC3066@ from .
dctRFC3066 :: ScopedName
dctRFC3066 = toDCT "RFC3066"
-- | @dcterms:RFC4646@ from .
dctRFC4646 :: ScopedName
dctRFC4646 = toDCT "RFC4646"
-- | @dcterms:RFC5646@ from .
dctRFC5646 :: ScopedName
dctRFC5646 = toDCT "RFC5646"
-- | @dcterms:URI@ from .
dctURI :: ScopedName
dctURI = toDCT "URI"
-- | @dcterms:W3CDTF@ from .
dctW3CDTF :: ScopedName
dctW3CDTF = toDCT "W3CDTF"
-- | @dcam:memberOf@ from .
dcammemberOf :: ScopedName
dcammemberOf = toDCAM "memberOf"
-- | @dcam:memberOf@ from .
dcamVocabularyEncodingScheme :: ScopedName
dcamVocabularyEncodingScheme = toDCAM "VocabularyEncodingScheme"
-- | @dctype:Collection@ from .
dctypeCollection :: ScopedName
dctypeCollection = toDCTYPE "Collection"
-- | @dctype:Dataset@ from .
dctypeDataset :: ScopedName
dctypeDataset = toDCTYPE "Dataset"
-- | @dctype:Event@ from .
dctypeEvent :: ScopedName
dctypeEvent = toDCTYPE "Event"
-- | @dctype:Image@ from .
dctypeImage :: ScopedName
dctypeImage = toDCTYPE "Image"
-- | @dctype:InteractiveResource@ from .
dctypeInteractiveResource :: ScopedName
dctypeInteractiveResource = toDCTYPE "InteractiveResource"
-- | @dctype:Service@ from .
dctypeService :: ScopedName
dctypeService = toDCTYPE "Service"
-- | @dctype:Software@ from .
dctypeSoftware :: ScopedName
dctypeSoftware = toDCTYPE "Software"
-- | @dctype:Sound@ from .
dctypeSound :: ScopedName
dctypeSound = toDCTYPE "Sound"
-- | @dctype:Text@ from .
dctypeText :: ScopedName
dctypeText = toDCTYPE "Text"
-- | @dctype:PhysicalObject@ from .
dctypePhysicalObject :: ScopedName
dctypePhysicalObject = toDCTYPE "PhysicalObject"
-- | @dctype:StillImage@ from .
dctypeStillImage :: ScopedName
dctypeStillImage = toDCTYPE "StillImage"
-- | @dctype:MovingImage@ from .
dctypeMovingImage :: ScopedName
dctypeMovingImage = toDCTYPE "MovingImage"
-- | @dcterms:LCSH@ from .
dctLCSH :: ScopedName
dctLCSH = toDCT "LCSH"
-- | @dcterms:MESH@ from .
dctMESH :: ScopedName
dctMESH = toDCT "MESH"
-- | @dcterms:DDC@ from .
dctDDC :: ScopedName
dctDDC = toDCT "DDC"
-- | @dcterms:LCC@ from .
dctLCC :: ScopedName
dctLCC = toDCT "LCC"
-- | @dcterms:UDC@ from .
dctUDC :: ScopedName
dctUDC = toDCT "UDC"
-- | @dcterms:DCMIType@ from .
dctDCMIType :: ScopedName
dctDCMIType = toDCT "DCMIType"
-- | @dcterms:IMT@ from .
dctIMT :: ScopedName
dctIMT = toDCT "IMT"
-- | @dcterms:TGN@ from .
dctTGN :: ScopedName
dctTGN = toDCT "TGN"
-- | @dcterms:NLM@ from .
dctNLM :: ScopedName
dctNLM = toDCT "NLM"
--------------------------------------------------------------------------------
--
-- Copyright (c) 2011 Douglas Burke
-- All rights reserved.
--
-- This file is part of Swish.
--
-- Swish is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- Swish is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with Swish; if not, write to:
-- The Free Software Foundation, Inc.,
-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
--
--------------------------------------------------------------------------------
swish-0.10.4.0/src/Swish/RDF/Vocabulary/FOAF.hs 0000644 0000000 0000000 00000035237 13543702315 016747 0 ustar 00 0000000 0000000 {-# LANGUAGE OverloadedStrings #-}
--------------------------------------------------------------------------------
-- See end of this file for licence information.
--------------------------------------------------------------------------------
-- |
-- Module : Swish.RDF.Vocabulary.FOAF
-- Copyright : (c) 2011 Douglas Burke
-- License : GPL V2
--
-- Maintainer : Douglas Burke
-- Stability : experimental
-- Portability : OverloadedStrings
--
-- This module defines some commonly used vocabulary terms from the FOAF
-- vocabulary ().
--
-- Note that unlike some of the existing vocabularies in Swish, the FOAF
-- one matches the case and spelling of the RDF terms; so we
-- use 'foafbased_near'
-- rather than @foafBasedNear@. This is partly because some terms would
-- end up with the same Haskell label if a conversion to camel-case wer
-- used.
--
--------------------------------------------------------------------------------
module Swish.RDF.Vocabulary.FOAF
(
-- | The version used for this module is
-- \"FOAF Vocabulary Specification 0.98 Namespace Document 9 August 2010 - /Marco Polo Edition/\",
-- .
namespaceFOAF
-- * Classes
, foafAgent
, foafDocument
, foafGroup
, foafImage
, foafLabelProperty
, foafOnlineAccount
, foafOnlineChatAccount
, foafOnlineEcommerceAccount
, foafOnlineGamingAccount
, foafOrganization
, foafPerson
, foafPersonalProfileDocument
, foafProject
-- * Properties
, foafaccount
, foafaccountName
, foafaccountServiceHomepage
, foafage
, foafaimChatID
, foafbased_near
, foafbirthday
, foafcurrentProject
, foafdepiction
, foafdepicts
, foafdnaChecksum
, foaffamilyName
, foaffamily_name
, foaffirstName
, foaffocus
, foaffundedBy
, foafgeekcode
, foafgender
, foafgivenName
, foafgivenname
, foafholdsAccount
, foafhomepage
, foaficqChatID
, foafimg
, foafinterest
, foafisPrimaryTopicOf
, foafjabberID
, foafknows
, foaflastName
, foaflogo
, foafmade
, foafmaker
, foafmbox
, foafmbox_sha1sum
, foafmember
, foafmembershipClass
, foafmsnChatID
, foafmyersBriggs
, foafname
, foafnick
, foafopenid
, foafpage
, foafpastProject
, foafphone
, foafplan
, foafprimaryTopic
, foafpublications
, foafschoolHomepage
, foafsha1
, foafskypeID
, foafstatus
, foafsurname
, foaftheme
, foafthumbnail
, foaftipjar
, foaftitle
, foaftopic
, foaftopic_interest
, foafweblog
, foafworkInfoHomepage
, foafworkplaceHomepage
, foafyahooChatID
)
where
import Swish.Namespace (Namespace, makeNamespace, ScopedName, makeNSScopedName)
import Swish.QName (LName)
import Data.Maybe (fromMaybe)
import Network.URI (URI, parseURI)
------------------------------------------------------------
-- Namespace
------------------------------------------------------------
foafURI :: URI
foafURI = fromMaybe (error "Internal error processing FOAF URI") $ parseURI "http://xmlns.com/foaf/0.1/"
-- | Maps @foaf@ to .
namespaceFOAF :: Namespace
namespaceFOAF = makeNamespace (Just "foaf") foafURI
------------------------------------------------------------
-- Terms
------------------------------------------------------------
toF :: LName -> ScopedName
toF = makeNSScopedName namespaceFOAF
-- Classes
-- | @foaf:Agent@ from .
foafAgent :: ScopedName
foafAgent = toF "Agent"
-- | @foaf:Document@ from .
foafDocument :: ScopedName
foafDocument = toF "Document"
-- | @foaf:Group@ from .
foafGroup :: ScopedName
foafGroup = toF "Group"
-- | @foaf:Image@ from .
foafImage :: ScopedName
foafImage = toF "Image"
-- | @foaf:LabelProperty@ from .
foafLabelProperty :: ScopedName
foafLabelProperty = toF "LabelProperty"
-- | @foaf:OnlineAccount@ from .
foafOnlineAccount :: ScopedName
foafOnlineAccount = toF "OnlineAccount"
-- | @foaf:OnlineChatAccount@ from .
foafOnlineChatAccount :: ScopedName
foafOnlineChatAccount = toF "OnlineChatAccount"
-- | @foaf:OnlineEcommerceAccount@ from .
foafOnlineEcommerceAccount :: ScopedName
foafOnlineEcommerceAccount = toF "OnlineEcommerceAccount"
-- | @foaf:OnlineGamingAccount@ from .
foafOnlineGamingAccount :: ScopedName
foafOnlineGamingAccount = toF "OnlineGamingAccount"
-- | @foaf:Organization@ from .
foafOrganization :: ScopedName
foafOrganization = toF "Organization"
-- | @foaf:Person@ from .
foafPerson :: ScopedName
foafPerson = toF "Person"
-- | @foaf:PersonalProfileDocument@ from .
foafPersonalProfileDocument :: ScopedName
foafPersonalProfileDocument = toF "PersonalProfileDocument"
-- | @foaf:Project@ from .
foafProject :: ScopedName
foafProject = toF "Project"
-- Properties
-- | @foaf:account@ from .
foafaccount :: ScopedName
foafaccount = toF "account"
-- | @foaf:accountName@ from .
foafaccountName :: ScopedName
foafaccountName = toF "accountName"
-- | @foaf:accountServiceHomepage@ from .
foafaccountServiceHomepage :: ScopedName
foafaccountServiceHomepage = toF "accountServiceHomepage"
-- | @foaf:age@ from .
foafage :: ScopedName
foafage = toF "age"
-- | @foaf:aimChatID@ from .
foafaimChatID :: ScopedName
foafaimChatID = toF "aimChatID"
-- | @foaf:based_near@ from .
foafbased_near :: ScopedName
foafbased_near = toF "based_near"
-- | @foaf:birthday@ from .
foafbirthday :: ScopedName
foafbirthday = toF "birthday"
-- | @foaf:currentProject@ from .
foafcurrentProject :: ScopedName
foafcurrentProject = toF "currentProject"
-- | @foaf:depiction@ from .
foafdepiction :: ScopedName
foafdepiction = toF "depiction"
-- | @foaf:depicts@ from .
foafdepicts :: ScopedName
foafdepicts = toF "depicts"
-- | @foaf:dnaChecksum@ from .
foafdnaChecksum :: ScopedName
foafdnaChecksum = toF "dnaChecksum"
-- | @foaf:familyName@ from .
foaffamilyName :: ScopedName
foaffamilyName = toF "familyName"
-- | @foaf:family_name@ from .
foaffamily_name :: ScopedName
foaffamily_name = toF "family_name"
-- | @foaf:firstName@ from .
foaffirstName :: ScopedName
foaffirstName = toF "firstName"
-- | @foaf:focus@ from .
foaffocus :: ScopedName
foaffocus = toF "focus"
-- | @foaf:fundedBy@ from .
foaffundedBy :: ScopedName
foaffundedBy = toF "fundedBy"
-- | @foaf:geekcode@ from .
foafgeekcode :: ScopedName
foafgeekcode = toF "geekcode"
-- | @foaf:gender@ from .
foafgender :: ScopedName
foafgender = toF "gender"
-- | @foaf:givenName@ from .
foafgivenName :: ScopedName
foafgivenName = toF "givenName"
-- | @foaf:givenname@ from .
foafgivenname :: ScopedName
foafgivenname = toF "givenname"
-- | @foaf:holdsAccount@ from .
foafholdsAccount :: ScopedName
foafholdsAccount = toF "holdsAccount"
-- | @foaf:homepage@ from .
foafhomepage :: ScopedName
foafhomepage = toF "homepage"
-- | @foaf:icqChatID@ from .
foaficqChatID :: ScopedName
foaficqChatID = toF "icqChatID"
-- | @foaf:img@ from .
foafimg :: ScopedName
foafimg = toF "img"
-- | @foaf:interest@ from .
foafinterest :: ScopedName
foafinterest = toF "interest"
-- | @foaf:isPrimaryTopicOf@ from .
foafisPrimaryTopicOf :: ScopedName
foafisPrimaryTopicOf = toF "isPrimaryTopicOf"
-- | @foaf:jabberID@ from .
foafjabberID :: ScopedName
foafjabberID = toF "jabberID"
-- | @foaf:knows@ from .
foafknows :: ScopedName
foafknows = toF "knows"
-- | @foaf:lastName@ from .
foaflastName :: ScopedName
foaflastName = toF "lastName"
-- | @foaf:logo@ from .
foaflogo :: ScopedName
foaflogo = toF "logo"
-- | @foaf:made@ from .
foafmade :: ScopedName
foafmade = toF "made"
-- | @foaf:maker@ from .
foafmaker :: ScopedName
foafmaker = toF "maker"
-- | @foaf:mbox@ from .
foafmbox :: ScopedName
foafmbox = toF "mbox"
-- | @foaf:mbox_sha1sum@ from .
foafmbox_sha1sum :: ScopedName
foafmbox_sha1sum = toF "mbox_sha1sum"
-- | @foaf:member@ from .
foafmember :: ScopedName
foafmember = toF "member"
-- | @foaf:membershipClass@ from