swish 0.6.1.2 → 0.6.2.1
raw patch · 116 files changed
+22685/−20121 lines, 116 filesPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
API changes (from Hackage documentation)
- Swish.RDF.RDFGraph: resRdfType, resLogImplies, resOwlSameAs, resRdfdMaxCardinality, resRdfdConstraint, resRdfdOnProperties, resRdfdGeneralRestriction, resRdfsMember, resRdfNil, resRdfRest, resRdfFirst :: RDFLabel
- Swish.RDF.Vocabulary: namespaceOWL :: Namespace
- Swish.RDF.Vocabulary: namespaceRDF :: Namespace
- Swish.RDF.Vocabulary: namespaceRDFS :: Namespace
- Swish.RDF.Vocabulary: namespaceXSD :: Namespace
- Swish.RDF.Vocabulary: owlSameAs :: ScopedName
- Swish.RDF.Vocabulary: rdfAbout :: ScopedName
- Swish.RDF.Vocabulary: rdfDatatype :: ScopedName
- Swish.RDF.Vocabulary: rdfFirst :: ScopedName
- Swish.RDF.Vocabulary: rdfID :: ScopedName
- Swish.RDF.Vocabulary: rdfNil :: ScopedName
- Swish.RDF.Vocabulary: rdfResource :: ScopedName
- Swish.RDF.Vocabulary: rdfRest :: ScopedName
- Swish.RDF.Vocabulary: rdfType :: ScopedName
- Swish.RDF.Vocabulary: rdfXMLLiteral :: ScopedName
- Swish.RDF.Vocabulary: rdfsMember :: ScopedName
- Swish.RDF.Vocabulary: scopeRDF, scopeRDFD, scopeRDFS :: Namespace
- Swish.RDF.Vocabulary: xsdBoolean :: ScopedName
- Swish.RDF.Vocabulary: xsdDate, xsdDateTime :: ScopedName
- Swish.RDF.Vocabulary: xsdDecimal :: ScopedName
- Swish.RDF.Vocabulary: xsdDouble :: ScopedName
- Swish.RDF.Vocabulary: xsdFloat :: ScopedName
- Swish.RDF.Vocabulary: xsdInteger :: ScopedName
- Swish.RDF.Vocabulary: xsdNegInteger :: ScopedName
- Swish.RDF.Vocabulary: xsdNonNegInteger :: ScopedName
- Swish.RDF.Vocabulary: xsdNonPosInteger :: ScopedName
- Swish.RDF.Vocabulary: xsdPosInteger :: ScopedName
- Swish.RDF.Vocabulary: xsdString :: ScopedName
- Swish.RDF.Vocabulary: xsdType :: Text -> ScopedName
+ Swish.RDF.RDFGraph: resLogImplies :: RDFLabel
+ Swish.RDF.RDFGraph: resOwlSameAs :: RDFLabel
+ Swish.RDF.RDFGraph: resRdf1 :: RDFLabel
+ Swish.RDF.RDFGraph: resRdf2 :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfAbout :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfAlt :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfBag :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfDatatype :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfDescription :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfFirst :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfID :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfLi :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfList :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfNil :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfNodeID :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfObject :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfParseType :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfPredicate :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfProperty :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfRDF :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfResource :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfRest :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfSeq :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfStatement :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfSubject :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfType :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfValue :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfXMLLiteral :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfdConstraint :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfdGeneralRestriction :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfdMaxCardinality :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfdOnProperties :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfn :: Int -> RDFLabel
+ Swish.RDF.RDFGraph: resRdfsClass :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfsComment :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfsContainer :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfsContainerMembershipProperty :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfsDatatype :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfsDomain :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfsIsDefinedBy :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfsLabel :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfsLiteral :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfsMember :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfsRange :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfsResource :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfsSeeAlso :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfsSubClassOf :: RDFLabel
+ Swish.RDF.RDFGraph: resRdfsSubPropertyOf :: RDFLabel
+ Swish.RDF.SwishMain: instance Show SwishAction
+ Swish.RDF.Vocabulary: scopeRDF :: Namespace
+ Swish.RDF.Vocabulary: scopeRDFD :: Namespace
+ Swish.RDF.Vocabulary: scopeRDFS :: Namespace
+ Swish.RDF.Vocabulary.DublinCore: dcamVocabularyEncodingScheme :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dcammemberOf :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dcelemcontributor :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dcelemcoverage :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dcelemcreator :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dcelemdate :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dcelemdescription :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dcelemformat :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dcelemidentifier :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dcelemlanguage :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dcelempublisher :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dcelemrelation :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dcelemrights :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dcelemsource :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dcelemsubject :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dcelemtitle :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dcelemtype :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctAgent :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctAgentClass :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctBibliographicResource :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctBox :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctDCMIType :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctDDC :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctFileFormat :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctFrequency :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctIMT :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctISO3166 :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctISO639_2 :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctISO639_3 :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctJurisdiction :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctLCC :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctLCSH :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctLicenseDocument :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctLinguisticSystem :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctLocation :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctLocationPeriodOrJurisdiction :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctMESH :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctMediaType :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctMediaTypeOrExtent :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctMethodOfAccrual :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctMethodOfInstruction :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctNLM :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctPeriod :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctPeriodOfTime :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctPhysicalMedium :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctPhysicalResource :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctPoint :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctPolicy :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctProvenanceStatement :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctRFC1766 :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctRFC3066 :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctRFC4646 :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctRFC5646 :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctRightsStatement :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctSizeOrDuration :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctStandard :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctTGN :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctUDC :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctURI :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctW3CDTF :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctabstract :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctaccessRights :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctaccrualMethod :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctaccrualPeriodicity :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctaccrualPolicy :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctalternative :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctaudience :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctavailable :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctbibliographicCitation :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctconformsTo :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctcontributor :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctcoverage :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctcreated :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctcreator :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctdate :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctdateAccepted :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctdateCopyrighted :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctdateSubmitted :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctdescription :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dcteducationLevel :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctextent :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctformat :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dcthasFormat :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dcthasPart :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dcthasVersion :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctidentifier :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctinstructionalMethod :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctisFormatOf :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctisPartOf :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctisReferencedBy :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctisReplacedBy :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctisRequiredBy :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctisVersionOf :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctissued :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctlanguage :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctlicense :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctmediator :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctmedium :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctmodified :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctprovenance :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctpublisher :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctreferences :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctrelation :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctreplaces :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctrequires :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctrights :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctrightsHolder :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctsource :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctspatial :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctsubject :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dcttableOfContents :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dcttemporal :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dcttitle :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dcttype :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctvalid :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctypeCollection :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctypeDataset :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctypeEvent :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctypeImage :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctypeInteractiveResource :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctypeMovingImage :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctypePhysicalObject :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctypeService :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctypeSoftware :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctypeSound :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctypeStillImage :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: dctypeText :: ScopedName
+ Swish.RDF.Vocabulary.DublinCore: namespaceDCAM :: Namespace
+ Swish.RDF.Vocabulary.DublinCore: namespaceDCELEM :: Namespace
+ Swish.RDF.Vocabulary.DublinCore: namespaceDCTERMS :: Namespace
+ Swish.RDF.Vocabulary.DublinCore: namespaceDCTYPE :: Namespace
+ Swish.RDF.Vocabulary.FOAF: foafAgent :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafDocument :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafGroup :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafImage :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafLabelProperty :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafOnlineAccount :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafOnlineChatAccount :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafOnlineEcommerceAccount :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafOnlineGamingAccount :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafOrganization :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafPerson :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafPersonalProfileDocument :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafProject :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafaccount :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafaccountName :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafaccountServiceHomepage :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafage :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafaimChatID :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafbased_near :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafbirthday :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafcurrentProject :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafdepiction :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafdepicts :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafdnaChecksum :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foaffamilyName :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foaffamily_name :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foaffirstName :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foaffocus :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foaffundedBy :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafgeekcode :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafgender :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafgivenName :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafgivenname :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafholdsAccount :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafhomepage :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foaficqChatID :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafimg :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafinterest :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafisPrimaryTopicOf :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafjabberID :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafknows :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foaflastName :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foaflogo :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafmade :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafmaker :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafmbox :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafmbox_sha1sum :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafmember :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafmembershipClass :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafmsnChatID :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafmyersBriggs :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafname :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafnick :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafopenid :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafpage :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafpastProject :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafphone :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafplan :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafprimaryTopic :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafpublications :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafschoolHomepage :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafsha1 :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafskypeID :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafstatus :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafsurname :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foaftheme :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafthumbnail :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foaftipjar :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foaftitle :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foaftopic :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foaftopic_interest :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafweblog :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafworkInfoHomepage :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafworkplaceHomepage :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: foafyahooChatID :: ScopedName
+ Swish.RDF.Vocabulary.FOAF: namespaceFOAF :: Namespace
+ Swish.RDF.Vocabulary.Geo: geoPoint :: ScopedName
+ Swish.RDF.Vocabulary.Geo: geoSpatialThing :: ScopedName
+ Swish.RDF.Vocabulary.Geo: geolat :: ScopedName
+ Swish.RDF.Vocabulary.Geo: geolat_long :: ScopedName
+ Swish.RDF.Vocabulary.Geo: geolocation :: ScopedName
+ Swish.RDF.Vocabulary.Geo: geolong :: ScopedName
+ Swish.RDF.Vocabulary.Geo: namespaceGEO :: Namespace
+ Swish.RDF.Vocabulary.OWL: namespaceOWL :: Namespace
+ Swish.RDF.Vocabulary.OWL: owlAnnotationProperty :: ScopedName
+ Swish.RDF.Vocabulary.OWL: owlClass :: ScopedName
+ Swish.RDF.Vocabulary.OWL: owlDatatypeProperty :: ScopedName
+ Swish.RDF.Vocabulary.OWL: owlNamedIndividual :: ScopedName
+ Swish.RDF.Vocabulary.OWL: owlNothing :: ScopedName
+ Swish.RDF.Vocabulary.OWL: owlObjectProperty :: ScopedName
+ Swish.RDF.Vocabulary.OWL: owlOntology :: ScopedName
+ Swish.RDF.Vocabulary.OWL: owlSameAs :: ScopedName
+ Swish.RDF.Vocabulary.OWL: owlThing :: ScopedName
+ Swish.RDF.Vocabulary.OWL: owlbackwardCompatibleWith :: ScopedName
+ Swish.RDF.Vocabulary.OWL: owldeprecated :: ScopedName
+ Swish.RDF.Vocabulary.OWL: owlequivalentClass :: ScopedName
+ Swish.RDF.Vocabulary.OWL: owlequivalentProperty :: ScopedName
+ Swish.RDF.Vocabulary.OWL: owlimports :: ScopedName
+ Swish.RDF.Vocabulary.OWL: owlincompatibleWith :: ScopedName
+ Swish.RDF.Vocabulary.OWL: owlpriorVersion :: ScopedName
+ Swish.RDF.Vocabulary.OWL: owlrational :: ScopedName
+ Swish.RDF.Vocabulary.OWL: owlreal :: ScopedName
+ Swish.RDF.Vocabulary.OWL: owlversionInfo :: ScopedName
+ Swish.RDF.Vocabulary.RDF: namespaceRDF :: Namespace
+ Swish.RDF.Vocabulary.RDF: namespaceRDFS :: Namespace
+ Swish.RDF.Vocabulary.RDF: rdf1 :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdf2 :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfAbout :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfAlt :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfBag :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfDatatype :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfDescription :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfFirst :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfID :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfLi :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfList :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfNil :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfNodeID :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfObject :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfParseType :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfPredicate :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfProperty :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfRDF :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfResource :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfRest :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfSeq :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfStatement :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfSubject :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfType :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfValue :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfXMLLiteral :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfn :: Int -> ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfsClass :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfsComment :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfsContainer :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfsContainerMembershipProperty :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfsDatatype :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfsDomain :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfsIsDefinedBy :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfsLabel :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfsLiteral :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfsMember :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfsRange :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfsResource :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfsSeeAlso :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfsSubClassOf :: ScopedName
+ Swish.RDF.Vocabulary.RDF: rdfsSubPropertyOf :: ScopedName
+ Swish.RDF.Vocabulary.XSD: namespaceXSD :: Namespace
+ Swish.RDF.Vocabulary.XSD: xsdAnyURI :: ScopedName
+ Swish.RDF.Vocabulary.XSD: xsdBoolean :: ScopedName
+ Swish.RDF.Vocabulary.XSD: xsdByte :: ScopedName
+ Swish.RDF.Vocabulary.XSD: xsdDate :: ScopedName
+ Swish.RDF.Vocabulary.XSD: xsdDateTime :: ScopedName
+ Swish.RDF.Vocabulary.XSD: xsdDecimal :: ScopedName
+ Swish.RDF.Vocabulary.XSD: xsdDouble :: ScopedName
+ Swish.RDF.Vocabulary.XSD: xsdFloat :: ScopedName
+ Swish.RDF.Vocabulary.XSD: xsdInt :: ScopedName
+ Swish.RDF.Vocabulary.XSD: xsdInteger :: ScopedName
+ Swish.RDF.Vocabulary.XSD: xsdLong :: ScopedName
+ Swish.RDF.Vocabulary.XSD: xsdNegInteger :: ScopedName
+ Swish.RDF.Vocabulary.XSD: xsdNonNegInteger :: ScopedName
+ Swish.RDF.Vocabulary.XSD: xsdNonPosInteger :: ScopedName
+ Swish.RDF.Vocabulary.XSD: xsdPosInteger :: ScopedName
+ Swish.RDF.Vocabulary.XSD: xsdShort :: ScopedName
+ Swish.RDF.Vocabulary.XSD: xsdString :: ScopedName
+ Swish.RDF.Vocabulary.XSD: xsdTime :: ScopedName
+ Swish.RDF.Vocabulary.XSD: xsdType :: Text -> ScopedName
+ Swish.RDF.Vocabulary.XSD: xsdUnsignedByte :: ScopedName
+ Swish.RDF.Vocabulary.XSD: xsdUnsignedInt :: ScopedName
+ Swish.RDF.Vocabulary.XSD: xsdUnsignedLong :: ScopedName
+ Swish.RDF.Vocabulary.XSD: xsdUnsignedShort :: ScopedName
Files
- Data/Interned/URI.hs +0/−91
- Swish.hs +0/−79
- Swish/RDF.hs +0/−44
- Swish/RDF/BuiltInDatatypes.hs +0/−74
- Swish/RDF/BuiltInMap.hs +0/−52
- Swish/RDF/BuiltInRules.hs +0/−157
- Swish/RDF/ClassRestrictionRule.hs +0/−546
- Swish/RDF/Datatype.hs +0/−1064
- Swish/RDF/GraphClass.hs +0/−248
- Swish/RDF/GraphMatch.hs +0/−622
- Swish/RDF/GraphMem.hs +0/−166
- Swish/RDF/GraphPartition.hs +0/−570
- Swish/RDF/MapXsdInteger.hs +0/−69
- Swish/RDF/N3Formatter.hs +0/−941
- Swish/RDF/N3Parser.hs +0/−1145
- Swish/RDF/NTFormatter.hs +0/−247
- Swish/RDF/NTParser.hs +0/−427
- Swish/RDF/Proof.hs +0/−296
- Swish/RDF/RDFDatatype.hs +0/−202
- Swish/RDF/RDFDatatypeXsdInteger.hs +0/−539
- Swish/RDF/RDFDatatypeXsdString.hs +0/−333
- Swish/RDF/RDFGraph.hs +0/−1141
- Swish/RDF/RDFGraphShowM.hs +0/−57
- Swish/RDF/RDFParser.hs +0/−353
- Swish/RDF/RDFProof.hs +0/−380
- Swish/RDF/RDFProofContext.hs +0/−848
- Swish/RDF/RDFQuery.hs +0/−610
- Swish/RDF/RDFRuleset.hs +0/−546
- Swish/RDF/RDFVarBinding.hs +0/−187
- Swish/RDF/Rule.hs +0/−244
- Swish/RDF/Ruleset.hs +0/−153
- Swish/RDF/SwishCommands.hs +0/−422
- Swish/RDF/SwishMain.hs +0/−263
- Swish/RDF/SwishMonad.hs +0/−270
- Swish/RDF/SwishScript.hs +0/−1512
- Swish/RDF/TurtleFormatter.hs +0/−885
- Swish/RDF/TurtleParser.hs +0/−1062
- Swish/RDF/VarBinding.hs +0/−540
- Swish/RDF/Vocabulary.hs +0/−264
- Swish/Utils/ListHelpers.hs +0/−359
- Swish/Utils/LookupMap.hs +0/−454
- Swish/Utils/MiscHelpers.hs +0/−69
- Swish/Utils/Namespace.hs +0/−222
- Swish/Utils/PartOrderedCollection.hs +0/−386
- Swish/Utils/QName.hs +0/−277
- Swish/Utils/ShowM.hs +0/−75
- app/SwishApp.hs +90/−0
- src/Data/Interned/URI.hs +91/−0
- src/Swish.hs +41/−52
- src/Swish/RDF.hs +44/−0
- src/Swish/RDF/BuiltInDatatypes.hs +74/−0
- src/Swish/RDF/BuiltInMap.hs +52/−0
- src/Swish/RDF/BuiltInRules.hs +157/−0
- src/Swish/RDF/ClassRestrictionRule.hs +546/−0
- src/Swish/RDF/Datatype.hs +1064/−0
- src/Swish/RDF/GraphClass.hs +248/−0
- src/Swish/RDF/GraphMatch.hs +622/−0
- src/Swish/RDF/GraphMem.hs +166/−0
- src/Swish/RDF/GraphPartition.hs +570/−0
- src/Swish/RDF/MapXsdInteger.hs +69/−0
- src/Swish/RDF/N3Formatter.hs +941/−0
- src/Swish/RDF/N3Parser.hs +1145/−0
- src/Swish/RDF/NTFormatter.hs +247/−0
- src/Swish/RDF/NTParser.hs +427/−0
- src/Swish/RDF/Proof.hs +296/−0
- src/Swish/RDF/RDFDatatype.hs +202/−0
- src/Swish/RDF/RDFDatatypeXsdInteger.hs +539/−0
- src/Swish/RDF/RDFDatatypeXsdString.hs +333/−0
- src/Swish/RDF/RDFGraph.hs +1396/−0
- src/Swish/RDF/RDFGraphShowM.hs +57/−0
- src/Swish/RDF/RDFParser.hs +353/−0
- src/Swish/RDF/RDFProof.hs +380/−0
- src/Swish/RDF/RDFProofContext.hs +848/−0
- src/Swish/RDF/RDFQuery.hs +636/−0
- src/Swish/RDF/RDFRuleset.hs +546/−0
- src/Swish/RDF/RDFVarBinding.hs +187/−0
- src/Swish/RDF/Rule.hs +247/−0
- src/Swish/RDF/Ruleset.hs +175/−0
- src/Swish/RDF/SwishCommands.hs +422/−0
- src/Swish/RDF/SwishMain.hs +291/−0
- src/Swish/RDF/SwishMonad.hs +270/−0
- src/Swish/RDF/SwishScript.hs +1512/−0
- src/Swish/RDF/TurtleFormatter.hs +885/−0
- src/Swish/RDF/TurtleParser.hs +1062/−0
- src/Swish/RDF/VarBinding.hs +540/−0
- src/Swish/RDF/Vocabulary.hs +233/−0
- src/Swish/RDF/Vocabulary/DublinCore.hs +770/−0
- src/Swish/RDF/Vocabulary/FOAF.hs +466/−0
- src/Swish/RDF/Vocabulary/Geo.hs +109/−0
- src/Swish/RDF/Vocabulary/OWL.hs +175/−0
- src/Swish/RDF/Vocabulary/RDF.hs +325/−0
- src/Swish/RDF/Vocabulary/XSD.hs +201/−0
- src/Swish/Utils/ListHelpers.hs +359/−0
- src/Swish/Utils/LookupMap.hs +454/−0
- src/Swish/Utils/MiscHelpers.hs +69/−0
- src/Swish/Utils/Namespace.hs +222/−0
- src/Swish/Utils/PartOrderedCollection.hs +386/−0
- src/Swish/Utils/QName.hs +277/−0
- src/Swish/Utils/ShowM.hs +75/−0
- swish.cabal +365/−212
- tests/BuiltInMapTest.hs +13/−12
- tests/GraphPartitionTest.hs +8/−3
- tests/GraphTest.hs +18/−11
- tests/LookupMapTest.hs +15/−9
- tests/N3FormatterTest.hs +7/−9
- tests/N3ParserTest.hs +12/−10
- tests/NTTest.hs +4/−2
- tests/QNameTest.hs +18/−15
- tests/RDFDatatypeXsdIntegerTest.hs +6/−88
- tests/RDFGraphTest.hs +6/−17
- tests/RDFProofContextTest.hs +12/−28
- tests/RDFProofTest.hs +10/−29
- tests/RDFQueryTest.hs +11/−34
- tests/RDFRulesetTest.hs +31/−7
- tests/TestHelpers.hs +245/−0
- tests/VarBindingTest.hs +12/−92
− Data/Interned/URI.hs
@@ -1,91 +0,0 @@-{-# LANGUAGE TypeFamilies, FlexibleInstances #-}------------------------------------------------------------------------------------- See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : URI--- Copyright : (c) 2011 Douglas Burke--- License : GPL V2------ Maintainer : Douglas Burke--- Stability : experimental--- Portability : TypeFamilies, FlexibleInstances------ 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 each component-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 (Eq) -- DU {-# UNPACK #-} !URI deriving (Eq) - describe = DU- identify = InternedURI- identity (InternedURI i _) = i- 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- hash = hashWithSalt 5381 -- use the stringSalt value from Data.Hashable- 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 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.hs
@@ -1,79 +0,0 @@------------------------------------------------------------------------------------ See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : Swish--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 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.RDF.SwishScript".------ Users wishing to process RDF data directly may prefer to look at--- the following modules; "Swish.RDF", "Swish.RDF.TurtleParser",--- "Swish.RDF.N3Parser", "Swish.RDF.N3Formatter", "Swish.RDF.NTParser"--- and "Swish.RDF.NTFormatter".------ (1) Semantic web: <http://www.w3.org/2001/sw/>------ (2) CWM: <http://www.w3.org/2000/10/swap/doc/cwm.html>------ (3) Motivation: <http://www.w3.org/2000/10/swap/doc/Motivation.html>------ (4) Notation 3: <http://www.w3.org/TeamSubmission/2008/SUBM-n3-20080114/>------ (5) Turtle: <http://www.w3.org/TR/turtle/>------ (6) RDF: <http://www.w3.org/RDF/>--------------------------------------------------------------------------------------module Swish- (- module Swish.RDF.SwishMain- ) where--import Swish.RDF.SwishMain--- should this re-export Swish.RDF.SwishMain or other components?- --------------------------------------------------------------------------------------- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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/RDF.hs
@@ -1,44 +0,0 @@------------------------------------------------------------------------------------ See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : RDF--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke--- License : GPL V2------ Maintainer : Douglas Burke--- Stability : experimental--- Portability : FlexibleInstances, MultiParamTypeClasses, TypeSynonymInstances------ This module provides an in-memory RDF Graph (it re-exports--- "Swish.RDF.RDFGraph").--------------------------------------------------------------------------------------module Swish.RDF (module Swish.RDF.RDFGraph) where--import Swish.RDF.RDFGraph---------------------------------------------------------------------------------------- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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/RDF/BuiltInDatatypes.hs
@@ -1,74 +0,0 @@------------------------------------------------------------------------------------ See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : BuiltInDatatypes--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 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.BuiltInDatatypes- ( allDatatypes, findRDFDatatype )- where--import Swish.RDF.RDFDatatype (RDFDatatype)--import Swish.Utils.LookupMap (LookupMap(..), mapFindMaybe)-import Swish.Utils.Namespace (ScopedName)-import Swish.RDF.RDFDatatypeXsdString (rdfDatatypeXsdString)-import Swish.RDF.RDFDatatypeXsdInteger (rdfDatatypeXsdInteger)----------------------------------------------------------------- Declare datatype map---------------------------------------------------------------allDatatypes :: [RDFDatatype]-allDatatypes =- [ rdfDatatypeXsdString- , rdfDatatypeXsdInteger- ]--findRDFDatatype :: ScopedName -> Maybe RDFDatatype-findRDFDatatype nam = mapFindMaybe nam (LookupMap allDatatypes)----------------------------------------------------------------- Declare datatype subtypes map---------------------------------------------------------------{--allDatatypeSubtypes :: [xxx]-allDatatypeSubtypes = []--- [[[details TBD]]]--}---------------------------------------------------------------------------------------- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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/RDF/BuiltInMap.hs
@@ -1,52 +0,0 @@------------------------------------------------------------------------------------ See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : BuiltInMap--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 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.BuiltInMap- ( findRDFOpenVarBindingModifier- , findRDFDatatype- , rdfRulesetMap- , allRulesets, allDatatypeRulesets- )-where--import Swish.RDF.BuiltInDatatypes-import Swish.RDF.BuiltInRules---------------------------------------------------------------------------------------- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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/RDF/BuiltInRules.hs
@@ -1,157 +0,0 @@------------------------------------------------------------------------------------ See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : BuiltInRules--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 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.BuiltInRules- ( findRDFOpenVarBindingModifier- , rdfRulesetMap- , allRulesets, allDatatypeRulesets- )-where--import Swish.RDF.BuiltInDatatypes (allDatatypes)--import Swish.RDF.RDFVarBinding- ( RDFOpenVarBindingModify- , rdfVarBindingUriRef, rdfVarBindingBlank- , rdfVarBindingLiteral- , rdfVarBindingUntypedLiteral, rdfVarBindingTypedLiteral- , rdfVarBindingXMLLiteral, rdfVarBindingDatatyped- , rdfVarBindingMemberProp- )--import Swish.RDF.RDFRuleset (RDFRuleset, RDFRulesetMap)--import Swish.RDF.RDFProofContext- ( rulesetRDF- , rulesetRDFS- , rulesetRDFD )--import Swish.RDF.VarBinding- ( nullVarBindingModify- , makeVarFilterModify- , varFilterEQ, varFilterNE- )--import Swish.RDF.Datatype (typeRules, typeMkModifiers)-import Swish.Utils.LookupMap (LookupMap(..), mapFindMaybe)-import Swish.Utils.Namespace (ScopedName)----------------------------------------------------------------- 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)--}--findRDFOpenVarBindingModifier :: ScopedName -> Maybe RDFOpenVarBindingModify-findRDFOpenVarBindingModifier nam =- mapFindMaybe nam (LookupMap allOpenVarBindingModify)----------------------------------------------------------------- Lookup map for built-in rulesets---------------------------------------------------------------rdfRulesetMap :: RDFRulesetMap-rdfRulesetMap = LookupMap allRulesets--allRulesets :: [RDFRuleset]-allRulesets =- [ rulesetRDF- , rulesetRDFS- , rulesetRDFD- ]- ++ allDatatypeRulesets--allDatatypeRulesets :: [RDFRuleset]-allDatatypeRulesets = map typeRules allDatatypes---------------------------------------------------------------------------------------- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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/RDF/ClassRestrictionRule.hs
@@ -1,546 +0,0 @@-{-# LANGUAGE MultiParamTypeClasses, OverloadedStrings #-}------------------------------------------------------------------------------------- See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : ClassRestrictionRule--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke--- License : GPL V2------ Maintainer : Douglas Burke--- Stability : experimental--- Portability : MultiParamTypeClasses, 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.RDF.RDFGraph- ( RDFLabel(..)- , getScopedName- , RDFGraph- , getArcs- , merge- , toRDFGraph, emptyRDFGraph- , Arc(..)- , resRdfType- , resRdfdMaxCardinality- )--import Swish.RDF.RDFRuleset (RDFRule, makeRDFGraphFromN3Builder)-import Swish.RDF.RDFDatatype (RDFDatatypeVal, fromRDFLabel, toRDFLabel)--import Swish.RDF.RDFQuery- ( rdfQueryFind- , rdfFindValSubj, rdfFindPredVal, rdfFindPredInt- , rdfFindList- )--import Swish.RDF.RDFVarBinding (RDFVarBinding)-import Swish.RDF.Datatype (DatatypeVal(..), DatatypeRel(..), DatatypeRelFn)-import Swish.RDF.Rule ( Rule(..), bwdCheckInference)-import Swish.RDF.VarBinding (VarBinding(..))-import Swish.RDF.Vocabulary (namespaceRDFD)--import Swish.Utils.Namespace (Namespace, ScopedName, namespaceToBuilder)-import Swish.Utils.PartOrderedCollection- ( minima, maxima- , partCompareEq, partComparePair- , partCompareListMaybe- , partCompareListSubset- )--import Swish.Utils.LookupMap (LookupEntryClass(..), LookupMap(..),mapFindMaybe)-import Swish.Utils.ListHelpers (powerSet)--import Control.Monad (liftM)--import Data.Monoid (Monoid (..))-import Data.Maybe (fromJust, fromMaybe, mapMaybe)-import Data.List (delete, nub, (\\))--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- }--instance Eq ClassRestriction where- cr1 == cr2 = crName cr1 == crName cr2--instance Show ClassRestriction where- show cr = "ClassRestriction:" ++ show (crName cr)--instance LookupEntryClass ClassRestriction ScopedName ClassRestriction- where- newEntry (_,fn) = fn- keyVal cr = (crName cr, 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 =- liftM (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 . "- ]- --- Placeholder false graph for now.-falseGraph :: RDFGraph-falseGraph = makeRDFGraphFromN3Builder $- mkPrefix namespaceRDFD `mappend` falseGraphStr--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- rn = mapFindMaybe (getScopedName r) (LookupMap 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 = nub [Arc ci p v | vs <- nts, (p,v) <- zip props vs ]- \\ getArcs antgr- newgrs = if 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 = liftM (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- makeGraphs = map (toRDFGraph . (:[])) . (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 $ powerSet 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 supplied datatype and graph---------------------------------------------------------------makeRDFDatatypeRestrictionRules :: RDFDatatypeVal vt -> RDFGraph -> [RDFRule]-makeRDFDatatypeRestrictionRules dtval gr =- makeRDFClassRestrictionRules dcrs gr- where- dcrs = map (makeDatatypeRestriction dtval) (tvalRel dtval)---------------------------------------------------------------------------------------- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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/RDF/Datatype.hs
@@ -1,1064 +0,0 @@-{-# LANGUAGE ExistentialQuantification, MultiParamTypeClasses, OverloadedStrings #-}------------------------------------------------------------------------------------- See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : Datatype--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke--- License : GPL V2------ Maintainer : Douglas Burke--- Stability : experimental--- Portability : ExistentialQuantification, MultiParamTypeClasses, OverloadedStrings------ This module defines the structures used by Swish 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.RDF.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.RDF.Ruleset- ( Ruleset(..)- , getRulesetAxiom, getRulesetRule- )--import Swish.RDF.Rule- ( Formula(..)- , Rule(..)- )--import Swish.RDF.Vocabulary (swishName)-import Swish.Utils.Namespace (ScopedName)--import Swish.RDF.VarBinding- ( VarBinding(..)- , addVarBinding- , VarBindingModify(..), OpenVarBindingModify, nullVarBindingModify- )--import Swish.Utils.LookupMap- ( LookupEntryClass(..), LookupMap(..)- , mapFindMaybe- )--import Swish.Utils.ListHelpers- ( flist- , deleteIndex- )---- used to add Show instances for structures during debugging--- but backed out again.------ import Swish.Utils.ShowM (ShowM(..))--import Data.Maybe( isJust, catMaybes )--import Control.Monad( join, liftM )--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)--instance LookupEntryClass- (Datatype ex lb vn) ScopedName (Datatype ex lb vn)- where- newEntry (_,dt) = dt- keyVal dt = (typeName dt, dt)---- |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--getDTRel ::- ScopedName -> DatatypeVal ex vt lb vn -> Maybe (DatatypeRel vt)-getDTRel nam dtv =- mapFindMaybe nam (LookupMap (tvalRel dtv))--getDTMod ::- ScopedName -> DatatypeVal ex vt lb vn -> Maybe (DatatypeMod vt lb vn)-getDTMod nam dtv =- mapFindMaybe nam (LookupMap (tvalMod dtv))---- |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 = join $ liftM (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- }--instance LookupEntryClass (DatatypeRel vt) ScopedName (DatatypeRel vt)- where- newEntry (_,relf) = relf- keyVal dtrel = (dtRelName dtrel, dtrel)---- |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- }--instance LookupEntryClass- (DatatypeMod vt lb vn) ScopedName (DatatypeMod vt lb vn)- where- newEntry (_,dmod) = dmod- keyVal dmod = (dmName dmod, dmod)---- |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 :: (Eq lb, Show lb, Eq vn, Show 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 :: (Eq lb, Show lb, Eq vn, Show 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 :: (Eq lb, Show lb, Eq vn, Show 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 :: (Eq lb, Show lb, Eq vn, Show 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 :: (Eq lb, Show lb, Eq vn, Show 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 :: (Eq lb, Show lb, Eq vn, Show 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 :: (Eq lb, Show lb, Eq vt, Show 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 :: (Eq lb, Show lb, Eq vt, Show 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 = liftM 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 =- liftM 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------------------------------------------------------------- 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 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/RDF/GraphClass.hs
@@ -1,248 +0,0 @@-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable #-}------------------------------------------------------------------------------------- See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : GraphClass--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke--- License : GPL V2------ Maintainer : Douglas Burke--- Stability : experimental--- Portability : DeriveFunctor, DeriveFoldable, DeriveTraversable, 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.RDF.GraphClass- ( LDGraph(..), replaceArcs- , Label(..)- , Arc(..), arcSubj, arcPred, arcObj, arc, arcToTriple, arcFromTriple- , Selector- , hasLabel, arcLabels -- , arcNodes- )-where--import qualified Data.Foldable as F-import qualified Data.Traversable as T--import Data.Hashable (Hashable(..))--import Data.List (foldl', union, (\\))---- 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: `setArcs`, `getArcs` and `containedIn`-(although @containedIn@ may be removed as it is currently unused).--}-class (Eq (lg lb), Eq lb ) => LDGraph lg lb- where- -- empty graph- -- emptyGr :: lg lb [[[TODO?]]]- -- component-level operations- - -- | Replace the existing arcs in the graph.- setArcs :: [Arc lb] -> lg lb -> lg lb- - -- | Extract all the arcs from a graph- getArcs :: lg lb -> [Arc lb]- - -- | Extract those arcs that match the given `Selector`.- extract :: Selector lb -> lg lb -> lg lb- extract sel = update (filter sel)- - -- | Add the two graphs- add :: lg lb -> lg lb -> lg lb- add addg = update (union (getArcs addg))- - -- | Remove those arcs in the first graph from the second- -- graph- delete :: lg lb -- ^ g1- -> lg lb -- ^ g2- -> lg lb -- ^ g2 - g1 -> g3- delete delg = update (\\ getArcs delg)- - -- | 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 :: lg lb -> [lb]- labels g = foldl' union [] (map arcLabels (getArcs g))- - -- | Enumerate the distinct nodes contained in a graph;- -- that is, any label that appears in the subject- -- or object position of an `Arc`.- nodes :: lg lb -> [lb]- nodes g = foldl' union [] (map arcNodes (getArcs g))- - -- | Test for graph containment in another.- --- -- At present this is unused and may be removed in a future release.- containedIn :: lg lb -> lg lb -> Bool - - -- | Update the arcs in a graph using a supplied function.- update :: ( [Arc lb] -> [Arc lb] ) -> lg lb -> lg lb- update f g = setArcs ( f (getArcs g) ) g--{--TODO:- add a Monoid instance for LDGraph, so that we can remove the- NSGraph instance in RDFGraph-- This means adding the emptyGr function to the interface--}---- |Function to replace arcs in a graph with a given list of arcs.------ This is identical to @flip setArcs@ and so may be removed.--- -replaceArcs :: (LDGraph lg lb) => lg lb -> [Arc lb] -> lg lb-replaceArcs = flip setArcs--- replaceArcs gr as = update (const as) gr---- | 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.--class (Eq lb, Show lb, Ord 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- - -- compare :: lb -> lb -> Ordering- -- compare l1 l2 = compare (show l1) (show l2)---- | Arc type--data Arc lb = Arc - { asubj :: lb -- ^ The subject of the arc.- , apred :: lb -- ^ The predicate (property) of the arc.- , aobj :: lb -- ^ The object of the arc.- }- deriving (Eq, Functor, F.Foldable, T.Traversable)--instance (Hashable lb) => Hashable (Arc lb) where- hash (Arc s p o) = hash s `hashWithSalt` p `hashWithSalt` o- hashWithSalt salt (Arc s p o) = salt `hashWithSalt` s `hashWithSalt` p `hashWithSalt` o---- | Return the subject of the arc.-arcSubj :: Arc lb -> lb-arcSubj = asubj---- | Return the predicate (property) of the arc.-arcPred :: Arc lb -> lb-arcPred = apred---- | Return the object of the arc.-arcObj :: Arc lb -> lb-arcObj = aobj---- | 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 (Arc s1 p1 o1) (Arc s2 p2 o2)- | cs /= EQ = cs- | cp /= EQ = cp- | otherwise = co- where- cs = compare s1 s2- cp = compare p1 p2- co = compare o1 o2-- {- not needed- (Arc s1 p1 o1) <= (Arc s2 p2 o2)- | s1 /= s2 = s1 <= s2- | p1 /= p2 = p1 <= p2- | otherwise = o1 <= o2- -}--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--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 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/RDF/GraphMatch.hs
@@ -1,622 +0,0 @@-{-# LANGUAGE FlexibleInstances, TypeSynonymInstances, MultiParamTypeClasses #-}------------------------------------------------------------------------------------- See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : GraphMatch--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke--- License : GPL V2------ Maintainer : Douglas Burke--- Stability : experimental--- Portability : FlexibleInstances, TypeSynonymInstances, MultiParamTypeClasses------ This module contains graph-matching logic.------ The algorithm used is derived from a paper on RDF graph matching--- by Jeremy Carroll [1].------ [1] <http://www.hpl.hp.com/techreports/2001/HPL-2001-293.html>--------------------------------------------------------------------------------------module Swish.RDF.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 Control.Exception.Base (assert)-import Control.Arrow (second)--import Data.Ord (comparing)-import Data.List (foldl', nub, sortBy, partition)-import Data.Hashable (combine)-import qualified Data.List as L--import Swish.RDF.GraphClass (Arc(..), Label(..),- arcLabels,- hasLabel, arcToTriple)-import Swish.Utils.LookupMap (LookupEntryClass(..), LookupMap(..),- makeLookupMap, listLookupMap, mapFind, mapReplaceAll,- mapAddIfNew, mapReplaceMap, mapMerge)-import Swish.Utils.ListHelpers (select, equiv, pairSort, pairGroup, pairUngroup)------------------------------- 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 = (Int,Int)--nullLabelVal :: LabelIndex-nullLabelVal = (0,0)---------------------------- Label mapping types--------------------------data (Label lb) => GenLabelEntry lb lv = LabelEntry lb lv--type LabelEntry lb = GenLabelEntry lb LabelIndex--instance (Label lb, Eq lb, Show lb, Eq lv, Show lv)- => LookupEntryClass (GenLabelEntry lb lv) lb lv where- keyVal (LabelEntry k v) = (k,v)- newEntry (k,v) = LabelEntry k v--instance (Label lb, Eq lb, Show lb, Eq lv, Show lv)- => Show (GenLabelEntry lb lv) where- show = entryShow--instance (Label lb, Eq lb, Show lb, Eq lv, Show lv)- => Eq (GenLabelEntry lb lv) where- (==) = entryEq---- | Type for label->index lookup table-data (Label lb, Eq lv, Show lv) => GenLabelMap lb lv =- LabelMap Int (LookupMap (GenLabelEntry lb lv))--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 == gen2 && es1 `equiv` es2- where- es1 = listLookupMap lmap1- es2 = listLookupMap lmap2--emptyMap :: (Label lb) => LabelMap lb-emptyMap = LabelMap 1 $ makeLookupMap []------------------------------- 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----------------------------------------------------------------- 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--makeScopedLabel :: (Label lb) => Int -> lb -> ScopedLabel lb-makeScopedLabel = ScopedLabel --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 `combine` scope -- MH.hash seed $ show 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)) ?---- | 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.)- -> [Arc lb] -- ^ the first graph to be compared, as a list of arcs- -> [Arc lb] -- ^ the second graph to be compared, as a list of arcs- -> (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 " $ -} map (makeScopedArc 1) gs1- sgs2 = {- trace "sgs2 " $ -} 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---- | 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.)------ 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.-----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.- -> [Arc lb] - -- ^ (@gs1@ argument)- -- first of two lists of arcs (triples) to be compared- -> [Arc 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)- -> [Arc lb] -> [Arc 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 = listLookupMap 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 = mapFind 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 l1)---- | 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,Int)] -> 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,Int) -> LabelMap lb-setLabelHash (LabelMap g lmap) (lb,lh) =- LabelMap g ( mapReplaceAll lmap $ newEntry (lb,(g,lh)) )---- | 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) => [lb] -> LabelMap lb -> LabelMap lb-assignLabelMap ns lmap = foldl' (flip assignLabelMap1) lmap ns--assignLabelMap1 :: (Label lb) => lb -> LabelMap lb -> LabelMap lb-assignLabelMap1 lab (LabelMap g lvs) = LabelMap g lvs'- where- lvs' = mapAddIfNew lvs $ newEntry (lab,(g,initVal lab))---- Calculate initial value for a node--initVal :: (Label lb) => lb -> Int-initVal = hashVal 0--hashVal :: (Label lb) => Int -> lb -> Int-hashVal seed lab =- if labelIsVar lab then seed `combine` 23 else labelHash seed lab- -- if labelIsVar lab then hash seed "???" else labelHash seed lab--equivalenceClasses :: - (Label lb) - => LabelMap lb -- ^ label map- -> [lb] -- ^ list of nodes to be reclassified- -> [EquivalenceClass lb]- -- ^ the equivalence classes of the supplied labels under the- -- supplied label map-equivalenceClasses lmap ls =- pairGroup $ 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) - => [Arc lb] - -- ^ (the @gs1@ argument) the first of two lists of arcs (triples) to perform a- -- basis for reclassifying the labels in the first equivalence- -- class in each pair of @ecpairs@.- -> [Arc lb]- -- ^ (the @gs2@ argument) the second of two lists of arcs (triples) 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' = mapReplaceMap lm $ mapMerge 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) = (mapFind nullLabelVal lab x,lab)---- | Calculate a new index value for a supplied list of labels based on the--- supplied label map and adjacency calculations in the supplied graph----remapLabels :: - (Label lb) - => [Arc 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' (LookupMap newEntries)- where- gen' = gen+1- newEntries = [ newEntry (l, (gen',newIndex l)) | l <- ls ]- newIndex l- | labelIsVar l = mapAdjacent l -- adjacency classifies variable labels- | otherwise = hashVal gen l -- otherwise rehash (to disentangle collisions)- -- mapAdjacent l = sum (sigsOver l) `rem` hashModulus- mapAdjacent l = sum (sigsOver l) `combine` hashModulus -- is this a sensible replacement for `rem` MH.hashModulus - sigsOver l = select (hasLabel l) gs (arcSignatures lmap gs)---- | Return list of distinct labels used in a graph--graphLabels :: (Label lb) => [Arc lb] -> [lb]-graphLabels = nub . concatMap arcLabels---- | 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 gs =- map (sigCalc . arcToTriple) gs- where- sigCalc (s,p,o) =- ( labelVal2 s +- labelVal2 p * 3 +- labelVal2 o * 5 )- `combine` hashModulus- -- `rem` hashModulus- - 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 ) `equiv` ( graphMap nodeMap gs2 ) then (same)----graphMap :: (Label lb) => LabelMap lb -> [Arc lb] -> [Arc LabelIndex]-graphMap = map . fmap . mapLabelIndex -- graphMapStmt---- | 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 -> [Arc lb] -> [Arc lb] -> Bool-graphMapEq lmap gs1 gs2 = graphMap lmap gs1 `equiv` graphMap lmap gs2---------------------------------------------------------------------------------------- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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/RDF/GraphMem.hs
@@ -1,166 +0,0 @@-{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-}-{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable #-}------------------------------------------------------------------------------------- See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : GraphMem--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke--- License : GPL V2------ Maintainer : Douglas Burke--- Stability : experimental--- Portability : DeriveFunctor, DeriveFoldable, DeriveTraversable, FlexibleInstances, MultiParamTypeClasses------ This module defines a simple memory-based graph instance.----------------------------------------------------------------------------------------------------------------------------------------------------- Simple labelled directed graph value---------------------------------------------------------------module Swish.RDF.GraphMem- ( GraphMem(..)- , setArcs, getArcs, add, delete, extract, labels- , LabelMem(..)- , labelIsVar, labelHash- -- For debug/test:- , matchGraphMem- ) where--import Swish.RDF.GraphClass-import Swish.RDF.GraphMatch--- import Swish.Utils.MiscHelpers (hash)--import Data.Hashable (Hashable(..), combine)-import Data.Ord (comparing)--import qualified Data.Foldable as F-import qualified Data.Traversable as T---- | Memory-based graph type --data GraphMem lb = GraphMem { arcs :: [Arc lb] }- deriving (Functor, F.Foldable, T.Traversable)- -instance (Label lb) => LDGraph GraphMem lb where- getArcs = arcs- setArcs as g = g { arcs=as }- -- gmap f g = g { arcs = (map $ fmap f) (arcs g) }- containedIn = error "containedIn for LDGraph GraphMem lb is undefined!" -- TODO: should there be one defined?--instance (Label lb) => Eq (GraphMem lb) where- (==) = graphEq--instance (Label lb) => Show (GraphMem lb) where- show = graphShow--graphShow :: (Label lb) => GraphMem lb -> String-graphShow g = "Graph:" ++ foldr ((++) . ("\n " ++) . show) "" (arcs g)--{--toGraph :: (Label lb) => [Arc lb] -> GraphMem lb-toGraph as = GraphMem { arcs=nub as }--}---- | 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- hash (LF l) = 1 `hashWithSalt` l- hash (LV l) = 2 `hashWithSalt` l- hashWithSalt salt (LF l) = salt `combine` 1 `hashWithSalt` l- hashWithSalt salt (LV l) = salt `combine` 2 `hashWithSalt` l--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 seed lb = hash seed (show lb)- labelHash = hashWithSalt--instance Eq LabelMem where- (LF l1) == (LF l2) = l1 == l2- (LV l1) == (LV l2) = l1 == l2- _ == _ = False--instance Show LabelMem where- show (LF l1) = '!' : l1- show (LV l2) = '?' : l2--instance Ord LabelMem where- compare = comparing show ---------------------------------------------------------------------------------------- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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/RDF/GraphPartition.hs
@@ -1,570 +0,0 @@------------------------------------------------------------------------------------ See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : GraphPartition--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke--- License : GPL V2------ Maintainer : Douglas Burke--- Stability : experimental--- Portability : H98------ This module contains functions for partitioning a graph into subgraphs--- that rooted from different subject nodes.--------------------------------------------------------------------------------------module Swish.RDF.GraphPartition- ( PartitionedGraph(..), getArcs, getPartitions- , GraphPartition(..), node, toArcs- , partitionGraph, comparePartitions- , partitionShowP- )-where--import Swish.RDF.GraphClass- ( Label(..)- , Arc(..), arcSubj, arcObj, - -- , hasLabel, arcLabels- )--import Data.List (foldl', partition)--import Control.Monad.State- ( MonadState(..), State, evalState )--import Data.Maybe (isJust, fromJust, mapMaybe)------------------------------------------------------------------ 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 (Eq,Show)--getArcs :: PartitionedGraph lb -> [Arc lb]-getArcs (PartitionedGraph ps) = concatMap toArcs ps--getPartitions :: PartitionedGraph lb -> [GraphPartition lb]-getPartitions (PartitionedGraph ps) = ps---- QUS: is the list always guaranteed to be non-empty in PartSub?-data GraphPartition lb- = PartObj lb- | PartSub lb [(lb,GraphPartition lb)]--node :: GraphPartition lb -> lb-node (PartObj ob) = ob-node (PartSub sb _) = sb--toArcs :: GraphPartition lb -> [Arc lb]-toArcs (PartObj _) = []-toArcs (PartSub sb prs) = concatMap toArcs1 prs- where- toArcs1 (pr,ob) = Arc sb pr (node ob) : toArcs ob--instance (Label lb) => Eq (GraphPartition lb) where- (==) = partitionEq--instance (Label lb) => Show (GraphPartition lb) where- show = partitionShow---- Equality is based on total structural equivalence.--- This is not the same as graph equality.-partitionEq :: (Label lb) => GraphPartition lb -> GraphPartition lb -> Bool-partitionEq (PartObj o1) (PartObj o2) = o1 == o2-partitionEq (PartSub s1 p1) (PartSub s2 p2) = s1 == s2 && p1 == p2-partitionEq _ _ = False--partitionShow :: (Label lb) => GraphPartition lb -> String-partitionShow (PartObj ob) = show ob-partitionShow (PartSub sb []) = "(" ++ show sb ++ ")" -- just to make -Wall happy, is this sensible?-partitionShow (PartSub sb (pr:prs)) =- "("++ show sb ++ " " ++ showpr pr ++ concatMap ((" ; "++).showpr) prs ++ ")"- where- showpr (a,b) = show a ++ " " ++ show b- -partitionShowP :: (Label lb) => String -> GraphPartition lb -> String-partitionShowP _ (PartObj ob) = show ob-partitionShowP pref (PartSub sb []) = pref ++ "(" ++ show sb ++ ")" -- just to make -Wall happy, is this sensible?-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 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 MakePartitionState lb = ([(lb,[Arc lb])],[(lb,[Arc lb])],[(lb,[Arc lb])])--makePartitions :: (Eq lb) =>- [(lb,[Arc lb])] -> [(lb,[Arc lb])] -> [(lb,[Arc 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 theinitial state of the--- monad (see call of evalState above).----makePartitions1 :: (Eq lb) =>- [(lb,[Arc lb])] -> State (MakePartitionState 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) =>- (lb,[Arc lb]) -> State (MakePartitionState lb) [GraphPartition lb]-makePartitions2 subs =- do { (part,moresubs) <- makeStatements subs- ; moreparts <- if not (null moresubs) then- makePartitions1 moresubs- else- return []- ; return $ part:moreparts- }--makeStatements :: (Eq lb) =>- (lb,[Arc lb])- -> State (MakePartitionState lb) (GraphPartition lb,[(lb,[Arc lb])])-makeStatements (sub,stmts) =- do { propmore <- mapM makeStatement stmts- ; let (props,moresubs) = unzip propmore- ; return (PartSub sub props,concat moresubs)- }--makeStatement :: (Eq lb) =>- Arc lb- -> State (MakePartitionState lb) ((lb,GraphPartition lb),[(lb,[Arc 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 :: State (MakePartitionState lb) [(lb,[Arc 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 -> State (MakePartitionState lb) [(lb,[Arc lb])]-pickIntSubject sub =- do { (s1,s2,s3) <- get- ; let varsub = removeBy (\x->(x==).fst) sub s3- ; if isJust varsub then- do { let (vs,s3new) = fromJust varsub- ; put (s1,s2,s3new)- ; return [vs]- }- else- return []- }--pickVarSubject :: (Eq lb) =>- lb -> State (MakePartitionState lb) [(lb,[Arc lb])]-pickVarSubject sub =- do { (s1,s2,s3) <- get- ; let varsub = removeBy (\x->(x==).fst) sub s2- ; if isJust varsub then- do { let (vs,s2new) = fromJust varsub- ; put (s1,s2new,s3)- ; return [vs]- }- else- 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 -> [(lb,GraphPartition lb)] -> [(lb,GraphPartition 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) s1s s2s--comparePartitions4 :: (Label lb) =>- lb -> lb -> (lb,GraphPartition lb) -> (lb,GraphPartition 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,[a])]-collect = collectBy (==)--collectBy :: (b->b->Bool) -> (a->b) -> [a] -> [(b,[a])]-collectBy cmp sel = map reverseCollection . collectBy1 cmp sel []--collectBy1 :: (b->b->Bool) -> (a->b) -> [(b,[a])] -> [a] -> [(b,[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,[a])] -> [(b,[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,[a]) -> (b,[a])-reverseCollection (k,as) = (k,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 remoived 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 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/RDF/MapXsdInteger.hs
@@ -1,69 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}------------------------------------------------------------------------------------- See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : MapXsdInteger--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 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.MapXsdInteger (mapXsdInteger) where--import Swish.RDF.Datatype (DatatypeMap(..))--import qualified Data.Text as T-import qualified Data.Text.Read as T----------------------------------------------------------------- Implementation of DatatypeMap for xsd:integer----------------------------------------------------------------- |mapXsdInteger contains 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 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/RDF/N3Formatter.hs
@@ -1,941 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}------------------------------------------------------------------------------------- See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : N3Formatter--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke--- License : GPL V2------ Maintainer : Douglas Burke--- Stability : experimental--- Portability : OverloadedStrings------ This Module implements a Notation 3 formatter (see [1], [2] and [3]),--- for an RDFGraph value.------ REFERENCES:------ (1) <http://www.w3.org/TeamSubmission/2008/SUBM-n3-20080114/>--- Notation3 (N3): A readable RDF syntax,--- W3C Team Submission 14 January 2008------ (2) <http://www.w3.org/DesignIssues/Notation3.html>--- Tim Berners-Lee's design issues series notes and description------ (2) <http://www.w3.org/2000/10/swap/Primer.html>--- 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.N3Formatter- ( NodeGenLookupMap- , formatGraphAsText- , formatGraphAsLazyText- , formatGraphAsBuilder- , formatGraphIndent - , formatGraphDiag- )-where--import Swish.RDF.RDFGraph (- RDFGraph, RDFLabel(..),- NamespaceMap, RevNamespaceMap,- emptyNamespaceMap,- FormulaMap, emptyFormulaMap,- getArcs, labels,- setNamespaces, getNamespaces,- getFormulae,- emptyRDFGraph- , quote- , quoteT- , resRdfFirst, resRdfRest, resRdfNil- )--import Swish.RDF.Vocabulary (- isLang, langTag, - rdfType,- rdfNil,- owlSameAs, logImplies- , xsdBoolean, xsdDecimal, xsdInteger, xsdDouble - )--import Swish.RDF.GraphClass (Arc(..))--import Swish.Utils.LookupMap- ( LookupEntryClass(..)- , LookupMap, emptyLookupMap, reverseLookupMap- , listLookupMap- , mapFind, mapFindMaybe, mapAdd, mapDelete, mapMerge- )--import Swish.Utils.Namespace (ScopedName, getScopeLocal, getScopeURI)--import Data.Char (isDigit)--import Data.List (foldl', delete, groupBy, partition, sort, intersperse)--import Data.Monoid (Monoid(..))-import Control.Monad (liftM, when)-import Control.Monad.State (State, modify, get, put, runState)---- 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.Text as T-import qualified Data.Text.Lazy as L-import qualified Data.Text.Lazy.Builder as B---- temporary conversion-quoteB :: Bool -> String -> B.Builder-quoteB f v = B.fromString $ quote f v--------------------------------------------------------------------------- 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).--type SubjTree lb = [(lb,PredTree lb)]-type PredTree lb = [(lb,[lb])]--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)]- , nodeGenSt :: NodeGenState- , bNodesCheck :: [RDFLabel] -- these bNodes are not to be converted to '[..]' format- , traceBuf :: [String]- }- -type Formatter a = State N3FormatterState a--emptyN3FS :: NodeGenState -> N3FormatterState-emptyN3FS ngs = N3FS- { indent = "\n"- , lineBreak = False- , graph = emptyRDFGraph- , subjs = []- , props = []- , objs = []- , formAvail = emptyFormulaMap- , formQueue = []- , nodeGenSt = ngs- , bNodesCheck = []- , traceBuf = []- }---- | Node name generation state information that carries through--- and is updated by nested formulae-type NodeGenLookupMap = LookupMap (RDFLabel,Int)--data NodeGenState = Ngs- { prefixes :: NamespaceMap- , nodeMap :: NodeGenLookupMap- , nodeGen :: Int- }--emptyNgs :: NodeGenState-emptyNgs = Ngs- { prefixes = emptyLookupMap- , nodeMap = emptyLookupMap- , nodeGen = 0- }---- simple context for label creation--- (may be a temporary solution to the problem--- of label creation)----data LabelContext = SubjContext | PredContext | ObjContext- deriving (Eq, Show)--getIndent :: Formatter B.Builder-getIndent = indent `liftM` get--setIndent :: B.Builder -> Formatter ()-setIndent ind = modify $ \st -> st { indent = ind }--getLineBreak :: Formatter Bool-getLineBreak = lineBreak `liftM` get--setLineBreak :: Bool -> Formatter ()-setLineBreak brk = modify $ \st -> st { lineBreak = brk }--getNgs :: Formatter NodeGenState-getNgs = nodeGenSt `liftM` get--setNgs :: NodeGenState -> Formatter ()-setNgs ngs = modify $ \st -> st { nodeGenSt = ngs }--getPrefixes :: Formatter NamespaceMap-getPrefixes = prefixes `liftM` getNgs--getSubjs :: Formatter (SubjTree RDFLabel)-getSubjs = subjs `liftM` get--setSubjs :: SubjTree RDFLabel -> Formatter ()-setSubjs sl = modify $ \st -> st { subjs = sl }--getProps :: Formatter (PredTree RDFLabel)-getProps = props `liftM` get--setProps :: PredTree RDFLabel -> Formatter ()-setProps ps = modify $ \st -> st { props = ps }--{--getObjs :: Formatter ([RDFLabel])-getObjs = objs `liftM` get--setObjs :: [RDFLabel] -> Formatter ()-setObjs os = do- st <- get- put $ st { objs = os }--}--getBnodesCheck :: Formatter [RDFLabel]-getBnodesCheck = bNodesCheck `liftM` get--{--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 = mapDelete fa fn,- formQueue = (fn,fv) : formQueue st- }- case mapFindMaybe fn fa of- Nothing -> return ()- Just v -> put (newState v) >> return ()--{--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 fa = formAvail st- newState = st { formAvail=mapDelete fa fn }- case mapFindMaybe fn fa of- Nothing -> return Nothing- Just fv -> put newState >> return (Just fv)--{--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---}---- list has a length of 1-len1 :: [a] -> Bool-len1 (_:[]) = True-len1 _ = False--{-|-Given a set of statements and a label, return the details of the-RDF collection referred to by label, or Nothing.--For label to be considered as representing a collection we require the-following conditions to hold (this is only to support the-serialisation using the '(..)' syntax and does not make any statement-about semantics of the statements with regard to RDF Collections):-- - there must be one rdf_first and one rdfRest statement- - there must be no other predicates for the label---} -getCollection :: - SubjTree RDFLabel -- ^ statements organized by subject- -> RDFLabel -- ^ does this label represent a list?- -> Maybe (SubjTree RDFLabel, [RDFLabel], [RDFLabel])- -- ^ the statements with the elements removed; the- -- content elements of the collection (the objects of the rdf:first- -- predicate) and the nodes that represent the spine of the- -- collection (in reverse order, unlike the actual contents which are in- -- order).-getCollection subjList lbl = go subjList lbl ([],[]) - where- go sl l (cs,ss) | l == resRdfNil = Just (sl, reverse cs, ss)- | otherwise = do- (pList1, sl') <- removeItem sl l- (pFirst, pList2) <- removeItem pList1 resRdfFirst- (pNext, pList3) <- removeItem pList2 resRdfRest-- -- QUS: could I include these checks implicitly in the pattern matches above?- -- ie instrad of (pFirst, pos1) <- ..- -- have ([content], pos1) <- ...- -- ?- if and [len1 pFirst, len1 pNext, null pList3]- then go sl' (head pNext) (head pFirst : cs, l : ss)- else Nothing--{--TODO:--Should we change the preds/objs entries as well?---}-extractList :: LabelContext -> RDFLabel -> Formatter (Maybe [RDFLabel])-extractList lctxt ln = do- osubjs <- getSubjs- oprops <- getProps- let mlst = getCollection osubjs' ln-- -- we only want to send in rdf:first/rdf:rest here- fprops = filter ((`elem` [resRdfFirst, resRdfRest]) . fst) oprops-- osubjs' =- case lctxt of- SubjContext -> (ln, fprops) : osubjs- _ -> osubjs -- -- tr = "extractList " ++ show ln ++ " (" ++ show lctxt ++ ")\n -> osubjs= " ++ show osubjs ++ "\n -> opreds= " ++ show oprops ++ "\n -> mlst= " ++ show mlst ++ "\n"- -- addTrace tr- case mlst of- -- sl is guaranteed to be free of (ln,fprops) here if lctxt is SubjContext- Just (sl,ls,_) -> do- setSubjs sl- when (lctxt == SubjContext) $ setProps $ filter ((`notElem` [resRdfFirst, resRdfRest]) . fst) oprops- return (Just ls)-- Nothing -> return Nothing- -{-|-Removes the first occurrence of the item from the-association list, returning it's contents and the rest-of the list, if it exists.--}-removeItem :: (Eq a) => [(a,b)] -> a -> Maybe (b, [(a,b)])-removeItem os x =- let (as, bs) = break (\a -> fst a == x) os- in case bs of- ((_,b):bbs) -> Just (b, as ++ bbs)- [] -> Nothing--------------------------------------------------------------------------- Define a top-level formatter function:-------------------------------------------------------------------------formatGraphAsText :: RDFGraph -> T.Text-formatGraphAsText = L.toStrict . formatGraphAsLazyText--formatGraphAsLazyText :: RDFGraph -> L.Text-formatGraphAsLazyText = B.toLazyText . formatGraphAsBuilder- -formatGraphAsBuilder :: RDFGraph -> B.Builder-formatGraphAsBuilder = formatGraphIndent "\n" True- -formatGraphIndent :: B.Builder -> Bool -> RDFGraph -> 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, Int, [String])-formatGraphDiag indnt flag gr = - let fg = formatGraph indnt " .\n" False flag gr- ngs = emptyNgs {- prefixes = emptyLookupMap,- nodeGen = findMaxBnode gr- }- - (out, fgs) = runState fg (emptyN3FS 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 ind end dobreak dopref gr = do- setIndent ind- setLineBreak dobreak- setGraph gr- - fp <- if dopref- then formatPrefixes (getNamespaces gr)- else return mempty- more <- moreSubjects- if more- then do- fr <- formatSubjects- return $ mconcat [fp, fr, end]- else return fp--formatPrefixes :: NamespaceMap -> Formatter B.Builder-formatPrefixes pmap = do- let mls = map (pref . keyVal) (listLookupMap pmap)- ls <- sequence mls- return $ mconcat ls- where- pref (Just p,u) = nextLine $ mconcat ["@prefix ", B.fromText p, ": <", quoteB True (show u), "> ."]- pref (_,u) = nextLine $ mconcat ["@prefix : <", quoteB True (show u), "> ."]--{--NOTE:-I expect there to be confusion below where I need to-convert from Text to Builder--}--formatSubjects :: Formatter B.Builder-formatSubjects = do- sb <- nextSubject- sbstr <- formatLabel SubjContext sb- - flagP <- moreProperties- if flagP- then do- prstr <- formatProperties sb sbstr- flagS <- moreSubjects- if flagS- then do- fr <- formatSubjects- return $ mconcat [prstr, " .", fr]- else return prstr- - else do- txt <- nextLine sbstr- - flagS <- moreSubjects- if flagS- then do- fr <- formatSubjects- return $ mconcat [txt, " .", fr]- else return txt--{--TODO: now we are throwing a Builder around it is awkward to-get the length of the text to calculate the indentation--So-- a) change the indentation scheme- b) pass around text instead of builder--mkIndent :: L.Text -> L.Text-mkIndent inVal = L.replicate (L.length inVal) " "--}--hackIndent :: B.Builder-hackIndent = " "--formatProperties :: RDFLabel -> B.Builder -> Formatter B.Builder-formatProperties sb sbstr = do- pr <- nextProperty sb- prstr <- formatLabel PredContext pr- obstr <- formatObjects sb pr $ mconcat [sbstr, " ", prstr]- more <- moreProperties- let sbindent = hackIndent -- mkIndent sbstr- if more- then do- fr <- formatProperties sb sbindent- nl <- nextLine $ obstr `mappend` " ;"- return $ nl `mappend` fr- else nextLine obstr--formatObjects :: RDFLabel -> RDFLabel -> B.Builder -> Formatter B.Builder-formatObjects sb pr prstr = do- ob <- nextObject sb pr- obstr <- formatLabel ObjContext ob- more <- moreObjects- if more- then do- let prindent = hackIndent -- mkIndent prstr- fr <- formatObjects sb pr prindent- nl <- nextLine $ mconcat [prstr, " ", obstr, ","]- return $ nl `mappend` fr- else return $ mconcat [prstr, " ", obstr]--insertFormula :: RDFGraph -> Formatter B.Builder-insertFormula gr = do- ngs0 <- getNgs- ind <- getIndent- let grm = formatGraph (ind `mappend` " ") "" True False- (setNamespaces emptyNamespaceMap gr)-- (f3str, fgs') = runState grm (emptyN3FS ngs0)-- setNgs (nodeGenSt fgs')- f4str <- nextLine " } "- return $ mconcat [" { ",f3str, f4str]--{--Add a list inline. We are given the labels that constitute-the list, in order, so just need to display them surrounded-by ().--}-insertList :: [RDFLabel] -> Formatter B.Builder-insertList [] = return "()" -- not convinced this can happen-insertList xs = do- ls <- mapM (formatLabel ObjContext) xs- return $ mconcat ("( " : intersperse " " ls) `mappend` " )"- -{--Add a blank node inline.--}--insertBnode :: LabelContext -> RDFLabel -> Formatter B.Builder-insertBnode SubjContext lbl = do- flag <- moreProperties- txt <- if flag- then (`mappend` "\n") `liftM` formatProperties lbl ""- else return ""-- -- TODO: handle indentation?- return $ mconcat ["[", txt, "]"]--insertBnode _ lbl = do- ost <- get- let osubjs = subjs ost- oprops = props ost- oobjs = objs ost-- (bsubj, rsubjs) = partition ((== lbl) . fst) osubjs-- rprops = case bsubj of- [(_,rs)] -> rs- _ -> []-- -- we essentially want to create a new subgraph- -- for this node but it's not as simple as that since- -- we could have something like- -- :a :b [ :foo [ :bar "xx" ] ]- -- so we still need to carry around the whole graph- --- nst = ost { subjs = rsubjs,- props = rprops,- objs = []- }-- put nst- flag <- moreProperties- txt <- if flag- then (`mappend` "\n") `liftM` formatProperties lbl ""- else return ""-- -- TODO: how do we restore the original set up?- -- I can't believe the following is sufficient- --- nst' <- get- let slist = map fst $ subjs nst'- nsubjs = filter (\(l,_) -> l `elem` slist) osubjs-- put $ nst' { subjs = nsubjs,- props = oprops, - objs = oobjs- }-- -- TODO: handle indentation?- return $ mconcat ["[", txt, "]"]- -------------------------------------------------------------------------- Formatting helpers-------------------------------------------------------------------------setGraph :: RDFGraph -> Formatter ()-setGraph gr = do- st <- get-- let ngs0 = nodeGenSt st- pre' = mapMerge (prefixes ngs0) (getNamespaces gr)- ngs' = ngs0 { prefixes = pre' }- arcs = sortArcs $ getArcs gr- nst = st { graph = gr- , subjs = arcTree arcs- , props = []- , objs = []- , formAvail = getFormulae gr- , nodeGenSt = ngs'- , bNodesCheck = countBnodes arcs- }-- put nst--hasMore :: (N3FormatterState -> [b]) -> Formatter Bool-hasMore lens = (not . null . lens) `liftM` get--moreSubjects :: Formatter Bool-moreSubjects = hasMore subjs--- moreSubjects = (not . null . subjs) `liftM` get--moreProperties :: Formatter Bool-moreProperties = hasMore props--- moreProperties = (not . null . props) `liftM` get--moreObjects :: Formatter Bool-moreObjects = hasMore objs--- moreObjects = (not . null . objs) `liftM` get--nextSubject :: Formatter RDFLabel-nextSubject = do- st <- get-- let sb:sbs = subjs st- nst = st { subjs = sbs- , props = snd sb- , objs = []- }-- put nst- return $ fst sb--nextProperty :: RDFLabel -> Formatter RDFLabel-nextProperty _ = do- st <- get-- let pr:prs = props st- nst = st { props = prs- , objs = snd pr- }-- put nst- return $ fst pr--nextObject :: RDFLabel -> RDFLabel -> Formatter RDFLabel-nextObject _ _ = do- st <- get-- let ob:obs = objs st- nst = st { objs = obs }-- put nst- return ob--nextLine :: B.Builder -> Formatter B.Builder-nextLine str = do- ind <- getIndent- brk <- getLineBreak- if brk- then return $ ind `mappend` str- else do- -- After first line, always insert line break- setLineBreak True- return str---- 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 <uri>--- (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.--}-formatLabel lctxt lab@(Blank (_:_)) = do- mlst <- extractList lctxt lab- case mlst of- Just lst -> insertList lst- Nothing -> do- mfml <- extractFormula lab- case mfml of- Just fml -> insertFormula fml- Nothing -> do- nb1 <- getBnodesCheck- 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 <- getPrefixes- let nsuri = getScopeURI sn- local = getScopeLocal sn- premap = reverseLookupMap pr :: RevNamespaceMap- prefix = mapFindMaybe nsuri premap- - name = case prefix of- Just (Just p) -> B.fromText $ quoteT True $ mconcat [p, ":", local] -- TODO: what are quoting rules for QNames- _ -> mconcat ["<", quoteB True (show nsuri ++ T.unpack local), ">"]- - {-- name = case prefix of- Just p -> quoteB True (p ++ ":" ++ local) -- TODO: what are quoting rules for QNames- _ -> mconcat ["<", quoteB True (nsuri++local), ">"]- -}- - queueFormula lab- return name---- The canonical notation for xsd:double in XSD, with an upper-case E,--- does not match the syntax used in N3, so we need to convert here. --- Rather than converting back to a Double and then displaying that --- we just convert E to e for now. --- -formatLabel _ (Lit lit (Just dtype)) - | dtype == xsdDouble = return $ B.fromText $ T.toLower lit- | dtype `elem` [xsdBoolean, xsdDecimal, xsdInteger] = return $ B.fromText lit- | otherwise = return $ quoteText lit `mappend` formatAnnotation dtype-formatLabel _ (Lit lit Nothing) = return $ quoteText lit--formatLabel _ lab = return $ B.fromString $ show lab---- the annotation for a literal (ie type or language)-formatAnnotation :: ScopedName -> B.Builder-formatAnnotation a | isLang a = "@" `mappend` B.fromText (langTag a)- | otherwise = "^^" `mappend` showScopedName a--{--We have to decide whether to use " or """ to quote-the string.--There is also no need to restrict the string to the-ASCII character set; this could be an option but we-can also leave Unicode as is (or at least convert to UTF-8).--If we use """ to surround the string then we protect the-last character if it is a " (assuming it isn't protected).--}--quoteText :: T.Text -> B.Builder-quoteText txt = - let st = T.unpack txt -- TODO: fix- qst = quoteB (n==1) st- n = if '\n' `elem` st || '"' `elem` st then 3 else 1- qch = B.fromString (replicate n '"')- in mconcat [qch, qst, qch]--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 lab = do- ngs <- getNgs- let cmap = nodeMap ngs- cval = nodeGen ngs- nv <- case mapFind 0 lab cmap of- 0 -> do - let nval = succ cval- nmap = mapAdd cmap (lab, nval)- setNgs $ ngs { nodeGen = nval, nodeMap = nmap }- return nval- - n -> return n- - -- TODO: is this what we want?- return $ "_:swish" `mappend` B.fromString (show nv)---- TODO: need to be a bit more clever with this than we did in NTriples--- not sure the following counts as clever enough ...--- -showScopedName :: ScopedName -> B.Builder-{--showScopedName (ScopedName n l) = - let uri = nsURI n ++ l- in quote uri--}-showScopedName = quoteB True . show--------------------------------------------------------------------------- Graph-related helper functions-------------------------------------------------------------------------newtype SortedArcs lb = SA [Arc lb]--sortArcs :: (Ord lb) => [Arc lb] -> SortedArcs lb-sortArcs = SA . sort---- Rearrange a list of arcs into a tree of pairs which group together--- all statements for a single subject, and similarly for multiple--- objects of a common predicate.----arcTree :: (Eq lb) => SortedArcs lb -> SubjTree lb-arcTree (SA as) = commonFstEq (commonFstEq id) $ map spopair as- where- spopair (Arc s p o) = (s,(p,o))--{--arcTree as = map spopair $ sort as- where- spopair (Arc s p o) = (s,[(p,[o])])--}---- Rearrange a list of pairs so that multiple occurrences of the first--- are commoned up, and the supplied function is applied to each sublist--- with common first elements to obtain the corresponding second value-commonFstEq :: (Eq a) => ( [b] -> c ) -> [(a,b)] -> [(a,c)]-commonFstEq f ps =- [ (fst $ head sps,f $ map snd sps) | sps <- groupBy fstEq ps ]- where- fstEq (f1,_) (f2,_) = f1 == f2--{---- Diagnostic code for checking arcTree logic:-testArcTree = (arcTree testArcTree1) == testArcTree2-testArcTree1 =- [Arc "s1" "p11" "o111", Arc "s1" "p11" "o112"- ,Arc "s1" "p12" "o121", Arc "s1" "p12" "o122"- ,Arc "s2" "p21" "o211", Arc "s2" "p21" "o212"- ,Arc "s2" "p22" "o221", Arc "s2" "p22" "o222"- ]-testArcTree2 =- [("s1",[("p11",["o111","o112"]),("p12",["o121","o122"])])- ,("s2",[("p21",["o211","o212"]),("p22",["o221","o222"])])- ]--}---findMaxBnode :: RDFGraph -> Int-findMaxBnode = maximum . map getAutoBnodeIndex . labels--getAutoBnodeIndex :: RDFLabel -> Int-getAutoBnodeIndex (Blank ('_':lns)) = res where- -- cf. prelude definition of read s ...- res = case [x | (x,t) <- reads lns, ("","") <- lex t] of- [x] -> x- _ -> 0-getAutoBnodeIndex _ = 0--{--Find all blank nodes that occur- - any number of times as a subject- - 0 or 1 times as an object--Such nodes can be output using the "[..]" syntax. To make it simpler-to check we actually store those nodes that can not be expanded.--Note that we do not try and expand any bNode that is used in-a predicate position.--Should probably be using the SubjTree RDFLabel structure but this-is easier for now.---}--countBnodes :: SortedArcs RDFLabel -> [RDFLabel]-countBnodes (SA as) = snd (foldl' ctr ([],[]) as)- where- -- first element of tuple are those blank nodes only seen once,- -- second element those blank nodes seen multiple times- --- inc b@(b1s,bms) l@(Blank _) | l `elem` bms = b- | l `elem` b1s = (delete l b1s, l:bms)- | otherwise = (l:b1s, bms)- inc b _ = b-- -- if the bNode appears as a predicate we instantly add it to the- -- list of nodes not to expand, even if only used once- incP b@(b1s,bms) l@(Blank _) | l `elem` bms = b- | l `elem` b1s = (delete l b1s, l:bms)- | otherwise = (b1s, l:bms)- incP b _ = b-- ctr orig (Arc _ p o) = inc (incP orig p) o---------------------------------------------------------------------------------------- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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/RDF/N3Parser.hs
@@ -1,1145 +0,0 @@-{-# LANGUAGE OverloadedStrings #-} -- only used in 'fromMaybe "" mbase' line of parseN3------------------------------------------------------------------------------------- See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : N3Parser--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke--- License : GPL V2------ Maintainer : Douglas Burke--- Stability : experimental--- Portability : OverloadedStrings------ This Module implements a Notation 3 parser (see [1], [2], [3]), returning a--- new 'RDFGraph' consisting of triples and namespace information parsed from--- the supplied N3 input string, or an error indication.------ REFERENCES:------ 1 <http://www.w3.org/TeamSubmission/2008/SUBM-n3-20080114/>--- Notation3 (N3): A readable RDF syntax,--- W3C Team Submission 14 January 2008------ 2 <http://www.w3.org/DesignIssues/Notation3.html>--- Tim Berners-Lee's design issues series notes and description------ 3 <http://www.w3.org/2000/10/swap/Primer.html>--- Notation 3 Primer by Sean Palmer------ NOTES:------ 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.N3Parser- ( 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.RDF.RDFGraph- ( RDFGraph, RDFLabel(..)- , ToRDFLabel(..)- , NamespaceMap- , LookupFormula(..) - , addArc - , setFormula- , setNamespaces- , emptyRDFGraph- )--import Swish.RDF.GraphClass (arc)--import Swish.Utils.LookupMap- ( LookupMap(..)- , LookupEntryClass(..)- , mapFind, mapFindMaybe, mapReplaceOrAdd, mapAdd, mapReplace )--import Swish.Utils.Namespace- ( Namespace, makeNamespace- , ScopedName- , getScopeNamespace- , getScopedNameURI- , getScopeNamespace- , makeURIScopedName- , makeQNameScopedName- , makeNSScopedName- , nullScopedName- )--import Swish.Utils.QName (QName)--import Swish.RDF.Vocabulary- ( langName- , rdfType- , rdfFirst, rdfRest, rdfNil- , owlSameAs, logImplies- , xsdBoolean, xsdInteger, xsdDecimal, xsdDouble- )--import Swish.RDF.RDFParser- ( SpecialMap- , ParseResult- , runParserWithError- -- , mapPrefix- , prefixTable- , specialTable- , ignore- , notFollowedBy- , endBy- , sepEndBy- , manyTill- , noneOf- , char- , ichar- , string- , stringT- , symbol- , lexeme- , whiteSpace- , mkTypedLit- , hex4 - , hex8 - , appendURIs- )--import Control.Applicative-import Control.Monad (forM_, foldM)--import Network.URI (URI(..), parseURIReference)--import Data.Char (isSpace, isDigit, ord) -import Data.Maybe (fromMaybe, fromJust)--import qualified Data.Text as T-import qualified Data.Text.Lazy as L-import Text.ParserCombinators.Poly.StateText--------------------------------------------------------------------------- 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 :: Int -- 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' = mapReplaceOrAdd (makeNamespace pre uri) (prefixUris st)---- | Set name for special syntax element-setSName :: String -> ScopedName -> N3State -> N3State-setSName nam snam st = st { syntaxUris=s' }- where- s' = mapReplaceOrAdd (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 = mapFind 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 = mapFindMaybe 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-------------------------------------------------------------------------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 = LookupMap [makeNamespace Nothing hashURI]- muri = fmap (makeQNameScopedName Nothing) mbase- smap = LookupMap $ 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)--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- -parseTextFromText :: String -> L.Text -> Either String String-parseTextFromText s =- parseAnyfromText (string s) Nothing--parseAltFromText :: String -> String -> L.Text -> Either String String-parseAltFromText s1 s2 =- parseAnyfromText (string s1 <|> string s2) Nothing--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 = LookupMap 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 ++ "'"--}--parseAbsURIrefFromText :: L.Text -> Either String URI-parseAbsURIrefFromText =- parseAnyfromText explicitURI Nothing--parseLexURIrefFromText :: L.Text -> Either String URI-parseLexURIrefFromText =- parseAnyfromText lexUriRef Nothing--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- mapReplaceOrAdd for the input namespace.- --}-operatorLabel :: ScopedName -> N3Parser RDFLabel-operatorLabel snam = do- st <- stGet- let sns = getScopeNamespace snam- opmap = prefixUris st- pkey = entryKey sns- pval = entryVal sns- - rval = Res snam- - -- the lookup and the replacement could be fused- case mapFindMaybe pkey opmap of- Just val | val == pval -> return rval- | otherwise -> do- stUpdate $ \s -> s { prefixUris = mapReplace opmap sns }- return rval- - _ -> do- stUpdate $ \s -> s { prefixUris = mapAdd opmap sns }- 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@(Lit _ (Just dtype)) | dtype `elem` [xsdBoolean, xsdInteger, xsdDecimal, xsdDouble] = do - ost <- stGet- let stmt = arc s p o- oldp = prefixUris ost- ogs = graphState ost- newp = mapReplaceOrAdd (getScopeNamespace dtype) 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 c = c >= 'a' && c <= 'z'-is09 c = c >= '0' && c <= '9'-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` "-_" || 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?-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' *> return '\t')- <|> (char 'n' *> return '\n')- <|> (char 'r' *> return '\r')- <|> (char '"' *> return '"')- <|> (char '\'' *> return '\'')- <|> (char '\\' *> return '\\')- <|> (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 = fmap T.pack (bracket sQuot sQuot $ many n3Character)- -{--tripleQUoted ::= """[^"\\]*(?:(?:\\.|"(?!""))[^"\\]*)*"""--}-tripleQuoted :: N3Parser T.Text-tripleQuoted = tQuot *> fmap T.pack (manyTill (n3Character <|> sQuot <|> char '\n') tQuot)- where- -- tQuot = try (count 3 sQuot)- tQuot = exactly 3 sQuot--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--}--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)- -}- -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- let lb = char '<'- rb = char '>'- - -- TODO: do the whitespace definitions match?- ustr <- between lb rb $ many (satisfy (/= '>'))- let uclean = filter (not . isSpace) ustr- - case parseURIReference uclean of- Nothing -> fail $ "Unable to convert <" ++ uclean ++ "> to a URI"- Just uref -> do- s <- stGet- let base = getSUri s "base"- either fail return $ appendURIs base uref- --- production from the old parser; used in SwishScript-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---}--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?--}--qname :: N3Parser ScopedName-qname =- fmap (uncurry makeNSScopedName) (char ':' >> g)- <|> (n3Name >>= fullOrLocalQName)- where- g = (,) <$> getDefaultPrefix <*> (n3Name <|> return "")- -fullOrLocalQName :: T.Text -> N3Parser ScopedName-fullOrLocalQName name = - (char ':' *> fullQName name)- <|> localQName name- -fullQName :: T.Text -> N3Parser ScopedName-fullQName name = makeNSScopedName <$> findPrefix name <*> (n3Name <|> pure "")- -findPrefix :: T.Text -> N3Parser Namespace-findPrefix pre = do- st <- stGet- case mapFindMaybe (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 makeNSScopedName <$> 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 *> pure ()--{--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 '!' *> return addStatementRev - forwardExpr = char '^' *> return 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 $ \_ -> 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- -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 = mkTypedLit 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 = Lit <$> n3string <*> optional dtlang- -dtlang :: N3Parser ScopedName-dtlang = - (char '@' *> langcode)- <|> string "^^" *> n3symbol- -- <|> (try (string "^^") *> n3symbol)--langcode :: N3Parser ScopedName-langcode = do- h <- many1Satisfy isaz- mt <- optional ( L.append <$> (char '-' *> pure (L.singleton '-')) <*> many1Satisfy isaz09)- return $ langName $ L.toStrict $ L.append h (fromMaybe L.empty mt)- -{--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 (mkTypedLit xsdDouble <$> n3double)- -- try (d2s <$> n3double)- -- <|> try (mkTypedLit xsdDecimal <$> n3decimal)- d2s <$> n3double- <|> mkTypedLit xsdDecimal . T.pack <$> n3decimal- <|> mkTypedLit 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") <*> 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 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/RDF/NTFormatter.hs
@@ -1,247 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}------------------------------------------------------------------------------------ See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : N3Formatter--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke--- License : GPL V2------ Maintainer : Douglas Burke--- Stability : experimental--- Portability : OverloadedStrings------ This Module implements a NTriples formatter (see [1])--- for an RDFGraph value.--------- REFERENCES:------ 1 <http://www.w3.org/TR/rdf-testcases/#ntriples>--- RDF Test Cases--- W3C Recommendation 10 February 2004--------------------------------------------------------------------------------------module Swish.RDF.NTFormatter- ( NodeGenLookupMap- , formatGraphAsText- , formatGraphAsLazyText- , formatGraphAsBuilder- )-where--import Swish.RDF.RDFGraph- ( RDFGraph, RDFLabel(..)- , getArcs- )--import Swish.RDF.GraphClass- ( Arc(..) )--import Swish.Utils.Namespace (ScopedName, getQName)-import Swish.RDF.Vocabulary (isLang, langTag)--import Swish.Utils.LookupMap- ( LookupMap, emptyLookupMap- , mapFind, mapAdd- )--import Data.Char (ord, intToDigit, toUpper)--import Control.Monad.State-import Control.Applicative ((<$>))-import Data.Monoid---- 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.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.---- | Node name generation state information that carries through--- and is updated by nested formulae-type NodeGenLookupMap = LookupMap (RDFLabel,Int)--data NTFormatterState = NTFS { - ntfsNodeMap :: NodeGenLookupMap,- ntfsNodeGen :: Int- } deriving Show--emptyNTFS :: NTFormatterState-emptyNTFS = NTFS {- ntfsNodeMap = emptyLookupMap,- ntfsNodeGen = 0- }--type Formatter a = State NTFormatterState a---- | 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 = fst $ runState (formatGraph gr) emptyNTFS--------------------------------------------------------------------------- Formatting as a monad-based computation-------------------------------------------------------------------------formatGraph :: RDFGraph -> Formatter B.Builder-formatGraph gr = mconcat <$> mapM formatArc (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]- -- return $ sl `mappend` $ space `mappend` $ pl `mappend` $ space `mappend` $ ol `mappend` nl- -{--If we have a blank node then can-- - use the label it contains- - generate a new one on output--For now we create new labels whatever the input was since this-simplifies things, but it may be changed.--formatLabel :: RDFLabel -> Formatter String-formatLabel lab@(Blank (lnc:_)) = - if isDigit lnc then mapBlankNode lab else return $ show lab-formatLabel lab = return $ show lab--}--squote, at, carets :: B.Builder-squote = "\""-at = "@"-carets = "^^"--formatLabel :: RDFLabel -> Formatter B.Builder-formatLabel lab@(Blank _) = mapBlankNode lab-formatLabel (Res sn) = return $ showScopedName sn-formatLabel (Lit lit Nothing) = return $ quoteText lit-formatLabel (Lit lit (Just nam)) | isLang nam = return $ mconcat [quoteText lit, at, B.fromText (langTag nam)]- | otherwise = return $ mconcat [quoteText lit, carets, showScopedName nam]---- 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 lab = do- st <- get- let cmap = ntfsNodeMap st- cval = ntfsNodeGen st-- nv <- case mapFind 0 lab cmap of- 0 -> do- let nval = succ cval- nmap = mapAdd cmap (lab, nval)-- put $ st { ntfsNodeMap = nmap, ntfsNodeGen = nval }- return nval-- n -> return n-- return $ "_:swish" `mappend` B.fromString (show nv)---- TODO: can we use Network.URI to protect the URI?-showScopedName :: ScopedName -> B.Builder-{--showScopedName (ScopedName n l) = - let uri = T.pack (show (nsURI n)) `mappend` l- in mconcat ["<", B.fromText (quote uri), ">"]--}--- showScopedName s = mconcat ["<", B.fromText (quote (T.pack (show (getQName s)))), ">"]-showScopedName s = B.fromText (quote (T.pack (show (getQName s)))) -- looks like qname already adds the <> around this--{--Swish.Utils.MiscHelpers contains a quote routine-which we expand upon here to match the NT syntax.--}--quoteText :: T.Text -> B.Builder-quoteText st = mconcat [squote, B.fromText (quote st), squote]--{--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 v = go [] v- 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 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/RDF/NTParser.hs
@@ -1,427 +0,0 @@------------------------------------------------------------------------------------ See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : NTParser--- Copyright : (c) 2011 Douglas Burke--- License : GPL V2------ Maintainer : Douglas Burke--- Stability : experimental--- Portability : H98------ This Module implements a NTriples parser (see [1]), returning a--- new 'RDFGraph' consisting of triples and namespace information parsed from--- the supplied NTriples input string, or an error indication.------ REFERENCES:------ 1 <http://www.w3.org/TR/rdf-testcases/#ntriples>--- RDF Test Cases--- W3C Recommendation 10 February 2004--------------------------------------------------------------------------------------module Swish.RDF.NTParser- ( ParseResult- , parseNT - , parsefromString- - {-- -- * Exports for parsers that embed NTriples in a bigger syntax- , NTParser, NTState(..)- , ntripleDoc- , line, ws, comment, eoln- , character, name, triple- , subject, predicate, object- , uriref, urirefLbl- , nodeID, literal, language- -}- - )-where--import Swish.RDF.RDFGraph- ( RDFGraph, RDFLabel(..)- , addArc - , emptyRDFGraph- )--import Swish.RDF.GraphClass (arc)--import Swish.Utils.Namespace (ScopedName, makeURIScopedName)--import Swish.RDF.Vocabulary (langName)--import Swish.RDF.RDFParser ( ParseResult- , runParserWithError- , ignore- , skipMany- , noneOf- , char- , string- , eoln- , fullStop- , hex4- , hex8- )- -{--import Swish.RDF.RDFParser- ( ParseResult, RDFParser- , ignore- , annotateParsecError- )--}--import Control.Applicative--import Network.URI (parseURI)--import qualified Data.Text as T-import qualified Data.Text.Lazy as L--import Data.Char (ord) -import Data.Maybe (fromMaybe)--import Text.ParserCombinators.Poly.StateText--------------------------------------------------------------------------- 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.----parsefromString :: - NTParser a -- ^ parser to apply- -> String -- ^ input to be parsed- -> Either String a-parsefromString parser = parsefromText parser . L.pack---- | Function to supply initial context and parse supplied term.----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")--{--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 c = c >= 'a' && c <= 'z'-isAZ c = c >= 'A' && c <= 'Z'-is09 c = c >= '0' && c <= '9'--isHeadChar, isBodyChar :: Char -> Bool-isHeadChar c = isaz c || isAZ c-isBodyChar c = isHeadChar c || is09 c--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 = - {- tryin to be fancy but addStatement is a Parser not a pure function- addStatement - <$> (subject <* skip1WS)- <*> (predicate <* skip1WS)- <*> (object <* (skipWS *> fullStop *> skipWS))- -}- - 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 in section URI References ---}--uriref :: NTParser ScopedName-uriref = do- -- not ideal, as want to reject invalid characters immediately rather than via parseURI- ustr <- L.unpack <$> bracket (char '<') (char '>') (many1Satisfy (/= '>'))- -- ustr <- bracket (char '<') (char '>') $ many1 character -- looks like need to exclude > from character- -- ustr <- char '<' *> manyTill character (char '>')- - maybe (failBad ("Invalid URI: <" ++ ustr ++ ">"))- (return . makeURIScopedName)- (parseURI ustr)--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 = Lit <$> (T.pack <$> ntstring) <*> optional dtlang--ntstring :: NTParser String-ntstring = bracket (char '"') (char '"') (many character)--dtlang :: NTParser ScopedName-dtlang = - (char '@' *> language)- <|> (string "^^" *> uriref)--language :: NTParser ScopedName-language = do- h <- many1Satisfy isaz- mt <- optional ( L.cons <$> char '-' <*> many1Satisfy (\c -> isaz c || is09 c) )- return $ langName $ L.toStrict $ L.append h $ fromMaybe L.empty mt--{--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' *> return '\t')- <|> (char 'n' *> return '\n')- <|> (char 'r' *> return '\r')- <|> (char '"' *> return '"')- <|> (char '\\' *> return '\\')- <|> (char 'u' *> hex4)- <|> (char 'U' *> hex8)--character :: NTParser Char-character = - (char '\\' *> protectedChar)- <|> satisfy isAsciiChar---------------------------------------------------------------------------------------- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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/RDF/Proof.hs
@@ -1,296 +0,0 @@------------------------------------------------------------------------------------ See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : Proof--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke--- License : GPL V2------ Maintainer : Douglas Burke--- Stability : experimental--- Portability : H98------ 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.RDF.Proof- ( Proof(..), Step(..)- , checkProof, explainProof, checkStep, showProof, showsProof, showsFormula )-where--import Swish.RDF.Ruleset (Ruleset(..))--import Swish.RDF.Rule- ( Expression(..), Formula(..), Rule(..)- , showsFormula, showsFormulae )--import Swish.Utils.ShowM (ShowM(..))--import Swish.Utils.ListHelpers (subset)--import Data.List (union, intersect, intercalate, foldl')-import Data.Maybe (catMaybes, isNothing)----------------------------------------------------------------- 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 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) => 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) => [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 :: - (Expression 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) => 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) => [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 :: - (Expression 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 (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 :: - (ShowM 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 :: - (ShowM 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 :: (ShowM 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 :: (ShowM 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 [] = "<nothing>"-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 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/RDF/RDFDatatype.hs
@@ -1,202 +0,0 @@------------------------------------------------------------------------------------ See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : RDFDatatype--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 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.RDFDatatype- ( RDFDatatype- , RDFDatatypeVal- , RDFDatatypeMod- , RDFModifierFn, RDFApplyModifier- , makeRdfDtOpenVarBindingModify, makeRdfDtOpenVarBindingModifiers- , applyRDFDatatypeMod- , RDFDatatypeSub- , fromRDFLabel, toRDFLabel, makeDatatypedLiteral- )-where--import Swish.RDF.RDFGraph- ( RDFLabel(..)- , isDatatyped- , getLiteralText- , RDFGraph- )--import Swish.RDF.RDFVarBinding- ( RDFVarBinding, RDFOpenVarBindingModify )--import Swish.RDF.Datatype- ( Datatype -- , typeName, typeRules- , DatatypeVal(..)- , DatatypeMap(..)- , DatatypeMod(..), ModifierFn- , ApplyModifier- , DatatypeSub(..)- )--import Swish.RDF.VarBinding (VarBindingModify(..))-import Swish.Utils.Namespace (ScopedName)--import Control.Monad (liftM)-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 =- liftM (makeDatatypedLiteral dtnam) . mapV2L dtmap- where- dtnam = tvalName dtv- dtmap = tvalMap dtv---- | Create a typed literal from the given value.-makeDatatypedLiteral :: ScopedName -> T.Text -> RDFLabel-makeDatatypedLiteral dtnam txtval =- Lit txtval (Just dtnam)---------------------------------------------------------------------------------------- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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/RDF/RDFDatatypeXsdInteger.hs
@@ -1,539 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}------------------------------------------------------------------------------------- See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : RDFDatatypeXsdInteger--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke--- License : GPL V2------ Maintainer : Douglas Burke--- Stability : experimental--- Portability : OverloadedStrings------ This module defines the structures used by Swish to represent and--- manipulate RDF @xsd:integer@ datatyped literals.--------------------------------------------------------------------------------------module Swish.RDF.RDFDatatypeXsdInteger- ( rdfDatatypeXsdInteger- , rdfDatatypeValXsdInteger- , typeNameXsdInteger, namespaceXsdInteger- , axiomsXsdInteger, rulesXsdInteger- , prefixXsdInteger- )-where--import Swish.RDF.RDFRuleset- ( RDFFormula, RDFRule, RDFRuleset - , makeRDFGraphFromN3Builder- , makeRDFFormula- )--import Swish.RDF.RDFDatatype- ( RDFDatatype- , RDFDatatypeVal- , RDFDatatypeMod- , makeRdfDtOpenVarBindingModifiers- )--import Swish.RDF.ClassRestrictionRule (makeRDFDatatypeRestrictionRules)-import Swish.RDF.MapXsdInteger (mapXsdInteger)--import Swish.RDF.Datatype- ( Datatype(..)- , DatatypeVal(..)- , DatatypeRel(..), DatatypeRelPr- , altArgs- , UnaryFnTable, unaryFnApp- , BinaryFnTable, binaryFnApp- , BinMaybeFnTable, binMaybeFnApp- , DatatypeMod(..) - , makeVmod11inv, makeVmod11- , makeVmod21inv, makeVmod21- , makeVmod20- , makeVmod22- )--import Swish.RDF.Ruleset (makeRuleset)--import Swish.Utils.Namespace (Namespace, ScopedName, namespaceToBuilder, makeNSScopedName)--import Swish.RDF.Vocabulary- ( namespaceRDF- , namespaceRDFS- , namespaceRDFD- , namespaceXSD- , namespaceXsdType- )--import Data.Monoid(Monoid(..))-import Control.Monad (liftM)-import Data.Maybe (maybeToList)--import qualified Data.Text as T-import qualified Data.Text.Lazy.Builder as B----------------------------------------------------------------- Misc values----------------------------------------------------------------- Local name for Integer datatype-nameXsdInteger :: T.Text-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---- Compose with function of two arguments-c2 :: (b -> c) -> (a -> d -> b) -> a -> d -> c-c2 = (.) . (.)---- 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----------------------------------------------------------------- Declare exported RDFDatatype value for xsd:integer---------------------------------------------------------------rdfDatatypeXsdInteger :: RDFDatatype-rdfDatatypeXsdInteger = Datatype rdfDatatypeValXsdInteger----------------------------------------------------------------- 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--- (<http://www.w3.org/2000/10/swap/doc/CwmBuiltins.html>).----relXsdInteger :: [DatatypeRel Integer]-relXsdInteger =- [ relXsdIntegerAbs- , relXsdIntegerNeg- , relXsdIntegerSum- , relXsdIntegerDiff- , relXsdIntegerProd- , relXsdIntegerDivMod- , relXsdIntegerPower- , relXsdIntegerEq- , relXsdIntegerNe- , relXsdIntegerLt- , relXsdIntegerLe- , relXsdIntegerGt- , relXsdIntegerGe- ]--mkIntRel2 ::- T.Text -> DatatypeRelPr Integer -> UnaryFnTable Integer- -> DatatypeRel Integer-mkIntRel2 nam pr fns = DatatypeRel- { dtRelName = makeNSScopedName namespaceXsdInteger nam- , dtRelFunc = altArgs pr fns unaryFnApp- }--mkIntRel3 ::- T.Text -> DatatypeRelPr Integer -> BinaryFnTable Integer- -> DatatypeRel Integer-mkIntRel3 nam pr fns = DatatypeRel- { dtRelName = makeNSScopedName namespaceXsdInteger nam- , dtRelFunc = altArgs pr fns binaryFnApp- }--mkIntRel3maybe ::- T.Text -> 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, [ ] )- ]--relXsdIntegerPower :: DatatypeRel Integer-relXsdIntegerPower = mkIntRel3maybe "power" (const True)- [ ( const True, [ (liftM (:[]) `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--- (<http://www.w3.org/2000/10/swap/doc/CwmBuiltins.html>).----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] = if r == 0 then [q] else []- 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 ::- T.Text -> (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--mkPrefix :: Namespace -> B.Builder-mkPrefix = namespaceToBuilder--prefixXsdInteger :: B.Builder-prefixXsdInteger = - mconcat- [ mkPrefix namespaceRDF- , mkPrefix namespaceRDFS- , mkPrefix namespaceRDFD- , mkPrefix namespaceXSD- , mkPrefix namespaceXsdInteger- ]--mkAxiom :: T.Text -> B.Builder -> RDFFormula-mkAxiom local gr =- makeRDFFormula namespaceXsdInteger local (prefixXsdInteger `mappend` gr)--axiomsXsdInteger :: [RDFFormula]-axiomsXsdInteger =- [ mkAxiom "dt" "xsd:integer rdf:type rdfs:Datatype ."- ]--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 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/RDF/RDFDatatypeXsdString.hs
@@ -1,333 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}------------------------------------------------------------------------------------- See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : RDFDatatypeXsdString--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke--- License : GPL V2------ Maintainer : Douglas Burke--- Stability : experimental--- Portability : OverloadedStrings------ This module defines the structures used by Swish to represent and--- manipulate RDF @xsd:string@ datatyped literals.---------------------------------------------------------------------------------------- TODO: this should convert to/from T.Text rather than String--module Swish.RDF.RDFDatatypeXsdString- ( rdfDatatypeXsdString- , rdfDatatypeValXsdString- , typeNameXsdString, namespaceXsdString- , axiomsXsdString, rulesXsdString- , prefixXsdString- )- where--import Swish.RDF.RDFRuleset- ( RDFFormula, RDFRule, RDFRuleset- , makeRDFGraphFromN3Builder- , makeRDFFormula- , makeN3ClosureRule- )--import Swish.RDF.RDFVarBinding (RDFVarBindingModify)--import Swish.RDF.RDFDatatype- ( RDFDatatype- , RDFDatatypeVal- , RDFDatatypeMod- , makeRdfDtOpenVarBindingModifiers- )--import Swish.RDF.RDFGraph (RDFLabel(..))-import Swish.RDF.ClassRestrictionRule (makeRDFDatatypeRestrictionRules)--import Swish.RDF.Datatype- ( Datatype(..)- , DatatypeVal(..)- , DatatypeMap(..)- , DatatypeRel(..), DatatypeRelPr- , altArgs- , UnaryFnTable, unaryFnApp- , DatatypeMod(..) - , makeVmod20- )--import Swish.RDF.Ruleset (makeRuleset)-import Swish.Utils.Namespace (Namespace, ScopedName, namespaceToBuilder, makeNSScopedName)--import Swish.RDF.Vocabulary- ( namespaceRDF- , namespaceRDFS- , namespaceRDFD- , namespaceXSD- , namespaceXsdType- )--import Swish.RDF.VarBinding (VarBinding(..), addVarBinding, VarBindingModify(..))--import Data.Monoid(Monoid(..))--import qualified Data.Text as T-import qualified Data.Text.Lazy.Builder as B----------------------------------------------------------------- Misc values----------------------------------------------------------------- Local name for Integer datatype-nameXsdString :: T.Text-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----------------------------------------------------------------- Declare exported RDFDatatype value for xsd:integer---------------------------------------------------------------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 ::- T.Text -> 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 ::- T.Text -> (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 :: T.Text -> B.Builder -> RDFFormula-mkAxiom local gr =- makeRDFFormula namespaceXsdString local (prefixXsdString `mappend` gr)--axiomsXsdString :: [RDFFormula]-axiomsXsdString =- [ mkAxiom "dt" "xsd:string rdf:type rdfs:Datatype ."- ]--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- app2 (Just (Lit s (Just _)))- (Just (Lit l Nothing))- vbind- | s == l- = [vbind]- app2 (Just (Lit s (Just _)))- Nothing- vbind- = [addVarBinding lvar (Lit s Nothing) vbind]- app2 Nothing- (Just (Lit l Nothing))- vbind- = [addVarBinding svar (Lit l (Just typeNameXsdString)) vbind]- app2 _ _ _ = []---------------------------------------------------------------------------------------- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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/RDF/RDFGraph.hs
@@ -1,1141 +0,0 @@-{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, TypeSynonymInstances #-}-{-# LANGUAGE OverloadedStrings #-}------------------------------------------------------------------------------------- See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : RDFGraph--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke--- License : GPL V2------ Maintainer : Douglas Burke--- Stability : experimental--- Portability : FlexibleInstances, MultiParamTypeClasses, TypeSynonymInstances, OverloadedStrings------ This module defines a memory-based RDF graph instance.----------------------------------------------------------------------------------------------------------------------------------------------------- Simple labelled directed graph value---------------------------------------------------------------module Swish.RDF.RDFGraph- ( - -- * Labels- RDFLabel(..), ToRDFLabel(..), FromRDFLabel(..)- , isLiteral, isUntypedLiteral, isTypedLiteral, isXMLLiteral- , isDatatyped, isMemberProp, isUri, isBlank, isQueryVar- , getLiteralText, getScopedName, makeBlank- , quote- , quoteT- - -- * RDF Graphs- , RDFTriple- , toRDFTriple, fromRDFTriple- , NSGraph(..)- , RDFGraph- , toRDFGraph, emptyRDFGraph {-, updateRDFGraph-}- , NamespaceMap, RevNamespaceMap, RevNamespace- , emptyNamespaceMap- , LookupFormula(..), Formula, FormulaMap, emptyFormulaMap- , addArc, merge- , allLabels, allNodes, remapLabels, remapLabelList- , newNode, newNodes- , setNamespaces, getNamespaces- , setFormulae, getFormulae, setFormula, getFormula- - -- * Re-export from GraphClass- , LDGraph(..), Label (..), Arc(..)- , arc, arcSubj, arcPred, arcObj, Selector- - -- * Export selected RDFLabel values- , resRdfType, resRdfFirst, resRdfRest, resRdfNil- , resRdfsMember- , resRdfdGeneralRestriction- , resRdfdOnProperties, resRdfdConstraint, resRdfdMaxCardinality- , resOwlSameAs, resLogImplies- - -- * Exported for testing- , grMatchMap, grEq- , mapnode, maplist- )- where--import Swish.Utils.Namespace- ( Namespace, makeNamespace, getNamespaceTuple- , getScopedNameURI- , ScopedName- , getScopeLocal, getScopeNamespace- , getQName- , makeQNameScopedName- , makeURIScopedName- , nullScopedName- )--import Swish.RDF.Vocabulary- ( namespaceRDF- , langTag, isLang- , rdfType- , rdfFirst, rdfRest, rdfNil, rdfXMLLiteral- , rdfsMember- , rdfdGeneralRestriction- , rdfdOnProperties, rdfdConstraint, rdfdMaxCardinality- , owlSameAs, logImplies- , xsdBoolean, xsdDecimal, xsdFloat, xsdDouble, xsdInteger- , xsdDateTime, xsdDate - )--import Swish.RDF.GraphClass- ( LDGraph(..), Label (..)- , Arc(..), arc, arcSubj, arcPred, arcObj, arcLabels- , Selector )--import Swish.RDF.GraphMatch (graphMatch, LabelMap, ScopedLabel(..))--import Swish.Utils.QName (QName)-import Swish.Utils.MiscHelpers (hash)-import Swish.Utils.ListHelpers (addSetElem)--import Swish.Utils.LookupMap- ( LookupMap(..), LookupEntryClass(..)- , listLookupMap- , mapFind, mapFindMaybe, mapReplaceOrAdd, mapAddIfNew- , mapVals, mapKeys )--import qualified Data.Foldable as Foldable-import qualified Data.Traversable as Traversable--import qualified Data.Text as T-import qualified Data.Text.Read as T--- import qualified Data.Text.Lazy as L--- import Data.Text.Format (format)--- import Data.Text.Buildable--- import Data.Text.Format.Types (Only(..))--import Control.Applicative (Applicative, liftA, (<$>), (<*>))--import Network.URI (URI)--import Data.Monoid (Monoid(..))-import Data.Maybe (mapMaybe)-import Data.Char (ord, isDigit)-import Data.List (intersect, union, findIndices, foldl')-import Data.Ord (comparing)--- import Data.Tuple (swap) not in 6.12.3-import Data.String (IsString(..))-import Data.Time (UTCTime, Day, ParseTime, parseTime, formatTime)-import System.Locale (defaultTimeLocale) -import Text.Printf--swap :: (a,b) -> (b,a)-swap (a,b) = (b,a)---- | RDF graph node values------ cf. <http://www.w3.org/TR/rdf-concepts/#section-Graph-syntax>------ 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)@.----data RDFLabel =- Res ScopedName -- ^ resource- | Lit T.Text (Maybe ScopedName) -- ^ literal [type/language]- | Blank String -- ^ blank node- | Var String -- ^ variable (not used in ordinary graphs)- | NoNode -- ^ no node (not used in ordinary graphs)---- TODO: should Lit be split up so that can easily differentiate between--- a type and a language tag?---- | 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,--- <http://www.w3.org/TR/2004/REC-rdf-concepts-20040210/#section-Literal-Equality>.----instance Eq RDFLabel where- Res q1 == Res q2 = q1 == q2- Blank b1 == Blank b2 = b1 == b2- Var v1 == Var v2 = v1 == v2-- Lit s1 Nothing == Lit s2 Nothing = s1 == s2- Lit s1 (Just t1) == Lit s2 (Just t2) = s1 == s2 && (t1 == t2 ||- (isLang t1 && isLang t2 &&- (T.toLower . langTag) t1 == (T.toLower . langTag) t2))- - _ == _ = False--instance Show RDFLabel where- show (Res sn) = show sn- show (Lit st Nothing) = quote1Str st- 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 = "<NoNode>"--instance Ord RDFLabel where- -- Optimize some common cases..- compare (Res sn1) (Res sn2) = compare sn1 sn2- compare (Blank ln1) (Blank ln2) = compare ln1 ln2- compare (Res _) (Blank _) = LT- compare (Blank _) (Res _) = GT- -- .. else use show string comparison- compare l1 l2 = comparing show l1 l2- - {- <= is not used if compare is provided- -- Similarly for <=- (Res qn1) <= (Res qn2) = qn1 <= qn2- (Blank ln1) <= (Blank ln2) = ln1 <= ln2- (Res _) <= (Blank _) = True- (Blank _) <= (Res _) = False- l1 <= l2 = show l1 <= show l2- -}- -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 (getScopeLocal sn)- getLocal (NoNode) = "None"- getLocal _ = "Lit_"- makeLabel ('?':loc) = Var loc- makeLabel loc = Blank loc- labelHash seed lb = hash seed (showCanon lb)--instance IsString RDFLabel where- fromString = flip Lit Nothing . 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-<http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#lexical-space>.- -Note that this is very similar to-'Swish.RDF.RDFDatatype.toRDFLabel' and should be moved into-a @Swish.RDF.Datatype@ module.--}--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 very similar to-'Swish.RDF.RDFDatatype.fromRDFLabel' and should be moved into-a @Swish.RDF.Datatype@ module.--}--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 . id`. -instance FromRDFLabel RDFLabel where- fromRDFLabel = Just . id- --- 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 (Lit xs (Just dt)) | dt == dtype = conv xs- | otherwise = Nothing-fLabel _ _ _ = Nothing- -tLabel :: (Show a) => ScopedName -> (String -> T.Text) -> a -> RDFLabel -tLabel dtype conv = flip Lit (Just dtype) . conv . show ---- | The character is converted to an untyped literal of length one.-instance ToRDFLabel Char where- toRDFLabel = flip Lit Nothing . T.singleton---- | The label must be an untyped literal containing a single character.-instance FromRDFLabel Char where- fromRDFLabel (Lit cs Nothing) | T.compareLength cs 1 == EQ = Just (T.head cs)- | otherwise = Nothing- fromRDFLabel _ = Nothing---- | Strings are converted to untyped literals.-instance ToRDFLabel String where- toRDFLabel = flip Lit Nothing . T.pack---- | Only untyped literals are converted to strings.-instance FromRDFLabel String where- fromRDFLabel (Lit xs Nothing) = 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 = Lit (if b then "true" else "false") (Just 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 Lit (Just 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 explicitlu 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"- pt f = parseTime defaultTimeLocale f inVal- 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 Lit (Just 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 Lit (Just 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.--- -showCanon :: RDFLabel -> String-showCanon (Res sn) = "<"++show (getScopedNameURI sn)++">"-showCanon (Lit st (Just nam))- | isLang nam = quote1Str st ++ "@" ++ T.unpack (langTag nam)- | otherwise = quote1Str st ++ "^^" ++ show (getScopedNameURI nam)-showCanon s = show s---- | See `quote`.-quoteT :: Bool -> T.Text -> T.Text-quoteT f = T.pack . quote f . T.unpack --{-| N3-style quoting rules for a string.--TODO: when flag is `False` need to worry about multiple quotes (> 2)-in a row.--}--quote :: - Bool -- ^ @True@ if the string is to be displayed using one rather than three quotes.- -> String -- ^ String to quote.- -> String-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 quotes ("...")--quote1Str :: T.Text -> String-quote1Str t = '"' : quote False (T.unpack t) ++ ['"']-------------------------------------------------------------- Selected RDFLabel values------------------------------------------------------------resRdfType, resRdfFirst, resRdfRest, resRdfNil,- resRdfsMember, resRdfdGeneralRestriction,- resRdfdOnProperties, resRdfdConstraint,- resRdfdMaxCardinality, resOwlSameAs, resLogImplies- :: RDFLabel--resRdfType = Res rdfType -resRdfFirst = Res rdfFirst -resRdfRest = Res rdfRest-resRdfNil = Res rdfNil-resRdfsMember = Res rdfsMember-resRdfdGeneralRestriction = Res rdfdGeneralRestriction-resRdfdOnProperties = Res rdfdOnProperties-resRdfdConstraint = Res rdfdConstraint-resRdfdMaxCardinality = Res rdfdMaxCardinality-resOwlSameAs = Res owlSameAs-resLogImplies = Res logImplies-------------------------------------------------------------- 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-isLiteral :: RDFLabel -> Bool-isLiteral (Lit _ _) = True-isLiteral _ = False---- |Test if supplied labal is an untyped literal node-isUntypedLiteral :: RDFLabel -> Bool-isUntypedLiteral (Lit _ Nothing ) = True-isUntypedLiteral (Lit _ (Just tn)) = isLang tn-isUntypedLiteral _ = False---- |Test if supplied labal is an untyped literal node-isTypedLiteral :: RDFLabel -> Bool-isTypedLiteral (Lit _ (Just tn)) = not (isLang tn)-isTypedLiteral _ = False---- |Test if supplied labal is an XML literal node-isXMLLiteral :: RDFLabel -> Bool-isXMLLiteral = isDatatyped rdfXMLLiteral---- |Test if supplied label is an typed literal node of a given datatype-isDatatyped :: ScopedName -> RDFLabel -> Bool-isDatatyped d (Lit _ (Just n)) = n == d-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 (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-getLiteralText :: RDFLabel -> T.Text-getLiteralText (Lit s _) = s-getLiteralText _ = ""---- |Extract ScopedName value from a resource node-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---- | 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--type NamespaceMap = LookupMap Namespace--data RevNamespace = RevNamespace Namespace--instance LookupEntryClass RevNamespace URI (Maybe T.Text) where- keyVal (RevNamespace ns) = swap $ getNamespaceTuple ns- newEntry (uri,pre) = RevNamespace (makeNamespace pre uri)--type RevNamespaceMap = LookupMap RevNamespace---- | Create an emoty namespace map.-emptyNamespaceMap :: NamespaceMap-emptyNamespaceMap = LookupMap []---- | 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 (Label lb)- => LookupEntryClass (LookupFormula lb (NSGraph lb)) lb (NSGraph lb)- where- keyVal fe = (formLabel fe, formGraph fe)- newEntry (k,v) = Formula { formLabel=k, formGraph=v }--instance (Label lb) => Show (LookupFormula lb (NSGraph lb))- where- show (Formula l g) = show l ++ " :- { " ++ showArcs " " g ++ " }"--type Formula lb = LookupFormula lb (NSGraph lb)--type FormulaMap lb = LookupMap (LookupFormula lb (NSGraph lb))---- | Create an empty formula map.-emptyFormulaMap :: FormulaMap RDFLabel-emptyFormulaMap = LookupMap []--{- given up on trying to do Functor for formulae...-instance Functor (LookupFormula (NSGraph lb)) where- fmap f fm = mapTranslateEntries (mapFormulaEntry f) fm--}--formulaeMap :: (lb -> l2) -> FormulaMap lb -> FormulaMap l2-formulaeMap f = fmap (formulaEntryMap f) --formulaEntryMap ::- (lb -> l2)- -> Formula lb- -> Formula l2-formulaEntryMap f (Formula k gr) = Formula (f k) (fmap f gr)--formulaeMapA :: Applicative f => (lb -> f l2) -> - FormulaMap lb -> f (FormulaMap l2)-formulaeMapA f = Traversable.traverse (formulaEntryMapA f)--formulaEntryMapA ::- (Applicative f) => - (lb -> f l2)- -> Formula lb- -> f (Formula l2)-formulaEntryMapA f (Formula k gr) = Formula `liftA` f k <*> Traversable.traverse f gr--{--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 :: [Arc lb] -- ^ the statements in the graph- }---- | 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 = mapFindMaybe l (formulae g)---- | Add (or replace) a formula.-setFormula :: (Label lb) => Formula lb -> NSGraph lb -> NSGraph lb-setFormula f g = g { formulae=mapReplaceOrAdd f (formulae g) }--instance (Label lb) => Monoid (NSGraph lb) where- mempty = NSGraph emptyNamespaceMap (LookupMap []) []- mappend = merge- -instance (Label lb) => LDGraph NSGraph lb where- getArcs = statements - setArcs as g = g { statements=as }- containedIn = error "containedIn for LDGraph NSGraph lb is undefined!" -- TODO: should there be one defined?--{-|-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 gr = gr { statements=addSetElem ar (statements gr) }--instance Functor NSGraph where- fmap f (NSGraph ns fml stmts) =- NSGraph ns (formulaeMap f fml) ((map $ fmap f) stmts)--instance Foldable.Foldable NSGraph where- foldMap = Traversable.foldMapDefault--instance Traversable.Traversable NSGraph where- traverse f (NSGraph ns fml stmts) = - NSGraph ns <$> formulaeMapA f fml <*> (Traversable.traverse $ Traversable.traverse f) stmts- -instance (Label lb) => Eq (NSGraph lb) where- (==) = grEq--instance (Label lb) => Show (NSGraph lb) where- show = grShow ""- showList = grShowList ""--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 = foldr ((++) . (pp ++) . show) "" fml- fml = listLookupMap (getFormulae g)- pp = "\n " ++ p--showArcs :: (Label lb) => String -> NSGraph lb -> String-showArcs p g = foldr ((++) . (pp ++) . show) "" (getArcs g)- where- pp = "\n " ++ p--grEq :: (Label lb) => NSGraph lb -> NSGraph lb -> Bool-grEq g1 = fst . grMatchMap g1--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- mapFormula g l = mapFindMaybe 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 = allLabels labelIsVar gr1- bn2 = allLabels labelIsVar gr2- dupbn = intersect bn1 bn2- allbn = union bn1 bn2- in- add 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 -> [lb]-allLabels p gr = 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 -> [lb]-allNodes p = unionNodes p [] . nodes---- | List all nodes in graph formulae satisfying a supplied predicate-formulaNodes :: (Label lb) => (lb -> Bool) -> NSGraph lb -> [lb]-formulaNodes p gr = foldl' (unionNodes p) fkeys (map (allLabels p) fvals)- where- -- fm :: (Label lb) => FormulaMap lb- -- LookupMap LookupFormula (NSGraph lb) lb- fm = formulae gr- -- fvals :: (Label lb) => [NSGraph lb]- fvals = mapVals fm- -- fkeys :: (Label lb) => [lb]- fkeys = filter p $ mapKeys fm---- | Helper to filter variable nodes and merge with those found so far-unionNodes :: (Label lb) => (lb -> Bool) -> [lb] -> [lb] -> [lb]-unionNodes p ls1 ls2 = ls1 `union` filter p ls2---- |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 = fmap (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 =- mapFind nv nv (LookupMap (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---- |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.)------ TODO: optimize this for common case @nnn@ and @_nnn@:--- always generate @_nnn@ and keep track of last allocated----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)--noderootindex :: (Label lb) => lb -> (String,Int)-noderootindex dn = (nh,nx) where- (nh,nt) = splitnodeid $ getLocal dn- nx = if null nt then 0 else read nt--splitnodeid :: String -> (String,String)-splitnodeid dn = splitAt (tx+1) dn where- tx = last $ (-1):findIndices (not . isDigit) dn--trynodes :: (Label lb) => (String,Int) -> [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 list 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 :: [RDFTriple] -> RDFGraph--- toRDFGraph arcs = emptyRDFGraph { statements = arcs }-toRDFGraph arcs = - let lbls = concatMap arcLabels arcs- - getNS (Res s) = Just s- getNS (Lit _ (Just tn)) | not (isLang tn) = Just tn- | otherwise = Nothing- getNS _ = Nothing-- ns = mapMaybe (fmap getScopeNamespace . getNS) lbls- nsmap = foldl' mapAddIfNew emptyNamespaceMap ns- - in mempty { namespaces = nsmap, statements = arcs }---- |Create a new, empty RDF graph.------ This uses `mempty` from the `Monoid` instance--- of `NSGraph`.----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 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/RDF/RDFGraphShowM.hs
@@ -1,57 +0,0 @@-{-# LANGUAGE TypeSynonymInstances #-}-{-# LANGUAGE FlexibleInstances #-} -- needed for ghc 7.2------------------------------------------------------------------------------------- See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : RDFGraphShowM--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke--- License : GPL V2------ Maintainer : Douglas Burke--- Stability : experimental--- Portability : TypeSynonymInstances, FlexibleInstances------ This module defines a `ShowM` class instance for `RDFGraph`, to be--- used when displaying RDF Graph values as part of a proof sequence,--- etc.--------------------------------------------------------------------------------------module Swish.RDF.RDFGraphShowM() where--import Swish.RDF.RDFGraph (RDFGraph)-import Swish.RDF.N3Formatter (formatGraphIndent)-import Swish.Utils.ShowM (ShowM(..))---- import qualified Data.Text.Lazy as L-import qualified Data.Text.Lazy.Builder as B--instance ShowM RDFGraph where- -- showms linebreak = shows . L.unpack . B.toLazyText . formatGraphIndent linebreak False - showms linebreak = shows . formatGraphIndent (B.fromString linebreak) False ---------------------------------------------------------------------------------------- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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/RDF/RDFParser.hs
@@ -1,353 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}------------------------------------------------------------------------------------ See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : RDFParser--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke--- License : GPL V2------ Maintainer : Douglas Burke--- Stability : experimental--- Portability : OverloadedStrings------ Support for the RDF Parsing modules.--------------------------------------------------------------------------------------module Swish.RDF.RDFParser- ( 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- , mkTypedLit- , hex4- , hex8- , appendURIs- )- where--import Swish.RDF.RDFGraph (RDFGraph, RDFLabel(..))-import Swish.RDF.Vocabulary- ( namespaceRDF- , namespaceRDFS- , namespaceRDFD- , namespaceOWL- , namespaceLOG- , rdfType- , rdfFirst, rdfRest, rdfNil- , owlSameAs, logImplies- , defaultBase- )--import Swish.Utils.LookupMap (LookupMap(..))-import Swish.Utils.Namespace (Namespace, makeNamespace, ScopedName)--import qualified Data.Text as T-import qualified Data.Text.Lazy as L-import qualified Data.Text.Read as R--import Text.ParserCombinators.Poly.StateText--import Network.URI (URI(..), relativeTo, parseURIReference)--import Data.Char (isSpace, isHexDigit, chr)-import Data.Maybe (fromMaybe, fromJust)---- Code---- | Append the two URIs. Should probably be moved--- out of RDFParser. It is also just a thin wrapper around--- `Network.URI.relativeTo`.--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- "" -> case uri `relativeTo` base of- Just out -> Right out- _ -> Left $ "Unable to append <" ++ show uri ++ "> to base=<" ++ show base ++ ">"- _ -> Right uri- --- | Type for special name lookup table-type SpecialMap = LookupMap (String,ScopedName)--{---- | Lookup prefix in table and return the matching URI.------ If the prefix is unknown then we currently error--- out (used to return 'prefix:' or ':' but now using--- URIs I am changing this behavior). This may well be--- backed out.-mapPrefix :: NamespaceMap -> Maybe String -> URI-mapPrefix pmap pfix = - case mapFindMaybe pfix pmap of- Just uri -> uri- Nothing -> error $ "Unable to find prefix: " ++ show pfix -- fromMaybe "" pfix ++ ":"--}- -{--mapPrefix ps p@(Just pre) = mapFind (pre++":") p ps-mapPrefix ps _ = mapFind ":" Nothing ps--}---- | 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.-The optional argument defines the initial base URI.--}-specialTable :: Maybe ScopedName -> [(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--type ParseResult = Either String RDFGraph--ignore :: (Applicative f) => f a -> f ()-ignore f = f *> pure ()--char :: Char -> Parser s Char-char c = satisfy (==c)--ichar :: Char -> Parser s ()-ichar = ignore . char---- TODO: is there a better way to do this?-string :: String -> Parser s String-string = mapM char- -stringT :: T.Text -> Parser s T.Text-stringT s = string (T.unpack s) >> return s--skipMany :: Parser s a -> Parser s ()-skipMany = ignore . many- -skipMany1 :: Parser s a -> Parser s ()-skipMany1 = ignore . many1- -endBy :: Parser s a -> Parser s b -> Parser s [a]-endBy p sep = many (p <* sep)--sepEndBy :: Parser s a -> Parser s b -> Parser s [a]-sepEndBy p sep = sepEndBy1 p sep <|> pure []---- is the separator optional?-sepEndBy1 :: Parser s a -> Parser s b -> Parser s [a]-sepEndBy1 p sep = do- x <- p- (sep *> ((x:) <$> sepEndBy p sep)) <|> return [x]- -manyTill :: Parser s a -> Parser s b -> Parser s [a]-manyTill p end = go- where- go = (end *> return [])- <|>- ((:) <$> p <*> go)---noneOf :: String -> Parser s Char -noneOf istr = satisfy (`notElem` istr)- -fullStop :: Parser s ()-fullStop = ichar '.'--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"])- -notFollowedBy :: (Char -> Bool) -> Parser s ()-notFollowedBy p = do- c <- next- if p c - then fail $ "Unexpected character: " ++ show [c]- else reparse $ L.singleton c--symbol :: String -> Parser s String-symbol = lexeme . string--isymbol :: String -> Parser s ()-isymbol = ignore . symbol--lexeme :: Parser s a -> Parser s a-lexeme p = p <* whiteSpace--whiteSpace :: Parser s ()-whiteSpace = skipMany (simpleSpace <|> oneLineComment)--simpleSpace :: Parser s ()-simpleSpace = ignore $ many1Satisfy isSpace--oneLineComment :: Parser s ()-oneLineComment = ichar '#' *> manySatisfy (/= '\n') *> pure ()--{---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---}---- | Create a typed literal.-mkTypedLit ::- ScopedName -- ^ the type- -> T.Text -- ^ the value- -> RDFLabel-mkTypedLit u v = Lit v (Just u)--{--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--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- -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 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/RDF/RDFProof.hs
@@ -1,380 +0,0 @@-{-# LANGUAGE FlexibleInstances, UndecidableInstances #-}------------------------------------------------------------------------------------ See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : RDFProof--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke--- License : GPL V2------ Maintainer : Douglas Burke--- Stability : experimental--- Portability : 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 <http://www.w3.org/TR/rdf-mt/>.--------------------------------------------------------------------------------------module Swish.RDF.RDFProof- ( RDFProof, RDFProofStep- , makeRDFProof, makeRDFProofStep- , makeRdfInstanceEntailmentRule- , makeRdfSubgraphEntailmentRule- , makeRdfSimpleEntailmentRule )-where--import Swish.RDF.RDFQuery- ( rdfQueryInstance- , rdfQuerySubs - )--import Swish.RDF.RDFRuleset- ( RDFFormula, RDFRule, RDFRuleset )--import Swish.RDF.RDFGraph- ( RDFLabel(..), RDFGraph- --, makeBlank- , merge , allLabels , remapLabelList- {-, newNode, newNodes- , toRDFGraph -}, emptyRDFGraph- )--import Swish.RDF.VarBinding (makeVarBinding)--import Swish.RDF.Proof- ( Proof(..), Step(..) )--import Swish.RDF.Rule- ( Expression(..), Rule(..) )--import Swish.Utils.Namespace (ScopedName)--import Swish.RDF.GraphClass- ( Label(..), LDGraph(..), replaceArcs )--import Swish.Utils.LookupMap- ( makeLookupMap, mapFind )--import Swish.Utils.ListHelpers- ( subset- , powerSet- , powerSequencesLen- , flist- )----------------------------------------------------------------- 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 (Label lb, LDGraph lg lb) => Expression (lg lb) where- isValid gr = null $ getArcs gr----------------------------------------------------------------- Define RDF-specific types for proof framework---------------------------------------------------------------type RDFProof = Proof RDFGraph--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]- -> 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 'allNodes' can be used to obtain a list of all--- the subjects and objuects used ina 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.--- This sequencing is determined by the powerset function used,--- which generates subsets in increasing order of size--- (see module 'ListHelpers').------ 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 emptyRDFGraph- else foldl1 merge ante- -- Obtain lists of variable and non-variable nodes- -- (was: nonvarNodes = allLabels (not . labelIsVar) mergeGraph)- nonvarNodes = vocab- varNodes = allLabels labelIsVar mergeGraph- -- Obtain list of possible remappings for non-variable nodes- mapList = remapLabelList nonvarNodes varNodes- mapSubLists = powerSet mapList- mapGr ls = fmap (\l -> mapFind l l (makeLookupMap 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 = 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 emptyRDFGraph -- 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.- any (mante ==) bsubs---- 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. This sequencing is determined--- by the 'powerSet' function used which generates subsets in--- increasing order of size (see module 'ListHelpers').-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 emptyRDFGraph- else foldl1 merge ante- in- -- Return all subgraphs of the full graph constructed above- map (replaceArcs mergeGraph) (init $ powerSet $ 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 emptyRDFGraph- else foldl1 add ante- in- -- Check each consequent graph arc is in the antecedent graph- getArcs cons `subset` 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 emptyRDFGraph else foldl1 add 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 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/RDF/RDFProofContext.hs
@@ -1,848 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}------------------------------------------------------------------------------------- See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : RDFProofContext--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke--- License : GPL V2------ Maintainer : Douglas Burke--- Stability : experimental--- Portability : 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.RDFProofContext ( rulesetRDF - , rulesetRDFS- , rulesetRDFD) where--import Swish.RDF.BuiltInDatatypes (findRDFDatatype)--import Swish.RDF.RDFProof- ( makeRdfSubgraphEntailmentRule- , makeRdfSimpleEntailmentRule )--import Swish.RDF.RDFRuleset - ( RDFFormula, RDFRule, RDFRuleset - , makeRDFFormula- , makeN3ClosureRule- , makeN3ClosureSimpleRule- , makeN3ClosureModifyRule- , makeN3ClosureAllocatorRule- , makeNodeAllocTo )--import Swish.RDF.RDFVarBinding- ( RDFVarBinding- , RDFVarBindingModify- , RDFVarBindingFilter- , rdfVarBindingUriRef, rdfVarBindingBlank- , rdfVarBindingLiteral- , rdfVarBindingUntypedLiteral - , rdfVarBindingXMLLiteral, rdfVarBindingDatatyped- , rdfVarBindingMemberProp- )--import Swish.RDF.RDFGraph- ( RDFLabel(..)- , isUri, isDatatyped- , getLiteralText )--import Swish.RDF.VarBinding- ( applyVarBinding- , addVarBinding- , VarBindingModify(..)- , makeVarFilterModify- , varFilterDisjunction- )--import Swish.RDF.Ruleset (makeRuleset)-import Swish.RDF.Datatype (typeMkCanonicalForm)-import Swish.Utils.Namespace (Namespace, makeNSScopedName)--import Swish.RDF.Vocabulary- ( namespaceRDFD- , scopeRDF- , scopeRDFS- , scopeRDFD- )--import Control.Monad (liftM)-import Data.Monoid (Monoid(..))-import Data.Maybe (isJust, fromJust)--import qualified Data.Text as T-import qualified Data.Text.Lazy.Builder as B----------------------------------------------------------------- Define query binding filter auxiliaries---------------------------------------------------------------makeFormula :: Namespace -> T.Text -> 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--{- -- 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- ]---- Define 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 "cp1"- "?x ?c ?y . "- "?c rdf:type rdfs:ContainerMembershipProperty ."- (makeVarFilterModify $ isMemberPropV "c")--rdfscp12 :: RDFRule-rdfscp12 = makeN3ClosureRule scopeRDFS "cp1"- "?c ?p ?y . "- "?c rdf:type rdfs:ContainerMembershipProperty ."- (makeVarFilterModify $ isMemberPropV "c")--rdfscp13 :: RDFRule-rdfscp13 = makeN3ClosureRule scopeRDFS "cp1"- "?x ?p ?c . "- "?c rdf:type rdfs:ContainerMembershipProperty ."- (makeVarFilterModify $ isMemberPropV "c")--rdfscp21 :: RDFRule-rdfscp21 = makeN3ClosureRule scopeRDFS "cp1"- "?x ?c ?y . "- "?c rdfs:domain rdfs:Resource ."- (makeVarFilterModify $ isMemberPropV "c")--rdfscp22 :: RDFRule-rdfscp22 = makeN3ClosureRule scopeRDFS "cp1"- "?c ?p ?y . "- "?c rdfs:domain rdfs:Resource ."- (makeVarFilterModify $ isMemberPropV "c")--rdfscp23 :: RDFRule-rdfscp23 = makeN3ClosureRule scopeRDFS "cp1"- "?x ?p ?c . "- "?c rdfs:domain rdfs:Resource ."- (makeVarFilterModify $ isMemberPropV "c")--rdfscp31 :: RDFRule-rdfscp31 = makeN3ClosureRule scopeRDFS "cp1"- "?x ?c ?y . "- "?c rdfs:range rdfs:Resource ."- (makeVarFilterModify $ isMemberPropV "c")--rdfscp32 :: RDFRule-rdfscp32 = makeN3ClosureRule scopeRDFS "cp1"- "?c ?p ?y . "- "?c rdfs:range rdfs:Resource ."- (makeVarFilterModify $ isMemberPropV "c")--rdfscp33 :: RDFRule-rdfscp33 = makeN3ClosureRule scopeRDFS "cp1"- "?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- ]---- Define 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- ]---- Define ruleset for RDFD inference----rulesetRDFD :: RDFRuleset-rulesetRDFD = makeRuleset scopeRDFD axiomsRDFD rulesRDFD---------------------------------------------------------------------------------------- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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/RDF/RDFQuery.hs
@@ -1,610 +0,0 @@------------------------------------------------------------------------------------ See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : RDFQuery--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke--- License : GPL V2------ Maintainer : Douglas Burke--- Stability : experimental--- Portability : H98------ 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.--------------------------------------------------------------------------------------module Swish.RDF.RDFQuery- ( rdfQueryFind, rdfQueryFilter- , rdfQueryBack, rdfQueryBackFilter, rdfQueryBackModify- , rdfQueryInstance- , rdfQuerySubs, rdfQueryBackSubs- , rdfQuerySubsAll- , rdfQuerySubsBlank, rdfQueryBackSubsBlank- , rdfFindArcs, rdfSubjEq, rdfPredEq, rdfObjEq- , rdfFindPredVal, rdfFindPredInt, rdfFindValSubj- , rdfFindList- -- debug- , rdfQuerySubs2 )-where--import Swish.RDF.RDFVarBinding- ( RDFVarBinding, nullRDFVarBinding- , RDFVarBindingFilter- )--import Swish.RDF.RDFGraph- ( Arc(..), LDGraph(..)- , arcSubj, arcPred, arcObj- , RDFLabel(..)- , isDatatyped, isBlank, isQueryVar- , getLiteralText, makeBlank- , RDFTriple- , RDFGraph, emptyRDFGraph- , allLabels, remapLabels- , resRdfFirst- , resRdfRest- , resRdfNil- )--import Swish.RDF.MapXsdInteger (mapXsdInteger)-import Swish.RDF.Datatype (DatatypeMap(..))--import Swish.RDF.VarBinding- ( VarBinding(..)- , makeVarBinding- , applyVarBinding, joinVarBindings- , VarBindingModify(..)- , VarBindingFilter(..)- )--import Swish.RDF.Vocabulary (xsdInteger, xsdNonNegInteger)--import Swish.Utils.ListHelpers (listProduct, allp, anyp)--import qualified Data.Traversable as Traversable--import Control.Monad.State (State, runState, modify)--import Data.Maybe (mapMaybe, isJust, fromJust)---- import qualified Data.Text as T----------------------------------------------------------------- Primitive RDF graph queries----------------------------------------------------------------- |Basic graph-query function.------ A very basic form of graph query, a query graph and--- a target graph, and returns a list of 'RDFVarBinding'--- values, each of which corresponds to a set of variable--- bindings that make the query graph a subgraph of the--- target graph, or @[]@ if the query cannot be matched.------ 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 -> RDFGraph -> [RDFVarBinding]-rdfQueryFind =- rdfQueryPrim1 matchQueryVariable nullRDFVarBinding . getArcs---- 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) (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 -> RDFGraph -> [[RDFVarBinding]]-rdfQueryBack qg tg =- rdfQueryBack1 matchQueryVariable [] (getArcs qg) (getArcs 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 listProduct 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 qbs = listProduct $ map (vbmApply qbm . (:[])) qbs----------------------------------------------------------------- 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 <http://www.w3.org/TR/rdf-mt/>, 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 . getArcs----------------------------------------------------------------- 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 = 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 = (add emptyRDFGraph g,vs)- where- (g,vs) = runState ( Traversable.traverse (mapNode varb) gr ) []---- 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 list of labels which accumulates all those--- variables seen for which no substitution was available.-mapNode :: RDFVarBinding -> RDFLabel -> State [RDFLabel] RDFLabel-mapNode varb lab =- case vbMap varb lab of- Just v -> return v- Nothing ->- if isQueryVar lab then- do { modify (addVar lab)- ; return lab- }- else- return lab---- Add variable to list of variables, if not already there-addVar :: RDFLabel -> [RDFLabel] -> [RDFLabel]-addVar var vars = if var `elem` vars then vars else var:vars----------------------------------------------------------------- Simple lightweight query primitives------------------------------------------------------------------- [[[TODO: modify above code to use these for all graph queries]]]---- |rdfFindArcs is the main function here: it takes 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:--- @rdfQuerySimple (rdfSubjEq s)@------ * find all statements with given property:--- @rdfQuerySimple (rdfPredEq p)@------ * find all statements with given object:--- @rdfQuerySimple (rdfObjEq o)@------ * find all statements matching conjunction of these conditions:--- @rdfQuerySimple ('allp' [...])@------ * find all statements matching disjunction of these conditions:--- @rdfQuerySimple ('anyp' [...])@------ Custom predicates can also be used.----rdfFindArcs :: (RDFTriple -> Bool) -> RDFGraph -> [RDFTriple]-rdfFindArcs p = 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 -> RDFLabel -> 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 -> RDFLabel -> 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 have a of given value for for a given predicate-rdfFindValSubj :: RDFLabel -> RDFLabel -> 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 some 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 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/RDF/RDFRuleset.hs
@@ -1,546 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}------------------------------------------------------------------------------------- See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : RDFRuleset--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke--- License : GPL V2------ Maintainer : Douglas Burke--- Stability : experimental--- Portability : 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.RDFRuleset- ( 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.RDF.RDFQuery- ( rdfQueryFind- , rdfQueryBack, rdfQueryBackModify- , rdfQuerySubs- , rdfQuerySubsBlank- )--import Swish.RDF.RDFGraph- ( RDFLabel(..), RDFGraph- , makeBlank, newNodes- , merge, allLabels- , toRDFGraph, emptyRDFGraph )--import Swish.RDF.RDFVarBinding (RDFVarBinding, RDFVarBindingModify)-import Swish.RDF.N3Parser (parseN3)-import Swish.RDF.Ruleset (Ruleset(..), RulesetMap)--import Swish.RDF.Rule- ( Formula(..), Rule(..), RuleMap- , fwdCheckInference- , nullSN- )--import Swish.RDF.VarBinding- ( makeVarBinding- , applyVarBinding, joinVarBindings- , VarBindingModify(..)- , vbmCompose- , varBindingId- )--import Swish.Utils.Namespace (Namespace, ScopedName, makeNSScopedName, namespaceToBuilder)-import Swish.RDF.Vocabulary (swishName, namespaceRDF, namespaceRDFS)--{--import Swish.RDF.Proof- ( Proof(..), Step(..) )--}--import Swish.RDF.GraphClass (Label(..), Arc(..), LDGraph(..))-import Swish.Utils.ListHelpers (equiv, flist)--import Data.List (nub)-import Data.Maybe (fromMaybe)-import Data.Monoid (Monoid(..))--import qualified Data.Text as T-import qualified Data.Text.Lazy.Builder as B----------------------------------------------------------------- Datatypes for RDF ruleset---------------------------------------------------------------type RDFFormula = Formula RDFGraph--type RDFRule = Rule RDFGraph--type RDFRuleMap = RuleMap RDFGraph--type RDFClosure = GraphClosure RDFLabel--type RDFRuleset = Ruleset RDFGraph--type RDFRulesetMap = RulesetMap RDFGraph----------------------------------------------------------------- Declare null RDF formula----------------------------------------------------------------- | The null RDF formula.-nullRDFFormula :: Formula RDFGraph-nullRDFFormula = Formula- { formName = nullSN "nullRDFGraph"- , formExpr = emptyRDFGraph- }----------------------------------------------------------------- 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 :: [Arc lb] -- ^ Antecedent triples pattern- -- (may include variable nodes)- , ruleCon :: [Arc 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.- }--instance (Label lb) => Eq (GraphClosure lb) where- c1 == c2 = nameGraphRule c1 == nameGraphRule c2 &&- ruleAnt c1 `equiv` ruleAnt c2 &&- ruleCon c1 `equiv` ruleCon c2--instance (Label lb) => 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 emptyRDFGraph else foldl1 add 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 add 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 :: [Arc RDFLabel] -> RDFGraph -> [RDFVarBinding]-queryFind qas = rdfQueryFind (toRDFGraph qas)--queryBack :: [Arc RDFLabel] -> RDFGraph -> [[RDFVarBinding]]-queryBack qas = rdfQueryBack (toRDFGraph qas)--querySubs :: [RDFVarBinding] -> [Arc RDFLabel] -> [RDFGraph]-querySubs vars = rdfQuerySubs vars . toRDFGraph--querySubsBlank :: [RDFVarBinding] -> [Arc RDFLabel] -> [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- -> T.Text -- ^ 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 = concatMap 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- -> T.Text -- ^ 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- -> T.Text -- ^ 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- -> T.Text -- ^ 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- -> T.Text -- ^ 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 (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 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/RDF/RDFVarBinding.hs
@@ -1,187 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}------------------------------------------------------------------------------------- See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : RDFVarBinding--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 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.RDFVarBinding- ( RDFVarBinding, nullRDFVarBinding- , RDFVarBindingModify, RDFOpenVarBindingModify, RDFOpenVarBindingModifyMap- , RDFVarBindingFilter- , rdfVarBindingUriRef, rdfVarBindingBlank- , rdfVarBindingLiteral- , rdfVarBindingUntypedLiteral, rdfVarBindingTypedLiteral- , rdfVarBindingXMLLiteral, rdfVarBindingDatatyped- , rdfVarBindingMemberProp- )-where--import Swish.RDF.RDFGraph- ( RDFLabel(..)- , isLiteral, isUntypedLiteral, isTypedLiteral, isXMLLiteral- , isDatatyped, isMemberProp, isUri, isBlank- )--import Swish.RDF.VarBinding- ( VarBinding(..), nullVarBinding- , applyVarBinding - , VarBindingModify(..), OpenVarBindingModify- , VarBindingFilter(..)- , makeVarTestFilter- )--import Swish.RDF.Vocabulary- ( swishName )--import Swish.Utils.LookupMap- ( LookupMap(..) )----------------------------------------------------------------- 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 = LookupMap 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 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/RDF/Rule.hs
@@ -1,244 +0,0 @@-{-# LANGUAGE MultiParamTypeClasses, OverloadedStrings #-}------------------------------------------------------------------------------------- See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : Rule--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke--- License : GPL V2------ Maintainer : Douglas Burke--- Stability : experimental--- Portability : MultiParamTypeClasses, OverloadedStrings------ This module defines a framework for defining inference rules--- 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.--------------------------------------------------------------------------------------module Swish.RDF.Rule- ( Expression(..), Formula(..), Rule(..), RuleMap- , nullScope, nullSN, nullFormula, nullRule- , fwdCheckInference, bwdCheckInference- , showsFormula, showsFormulae, showsWidth- )- where--import Swish.Utils.Namespace (Namespace, makeNamespace, ScopedName, makeScopedName)-import Swish.Utils.LookupMap (LookupEntryClass(..), LookupMap(..))-import Swish.Utils.ShowM (ShowM(..))--import Network.URI (URI, parseURI)-import Data.Maybe (fromJust)--import qualified Data.Text as T----------------------------------------------------------------- Expressions----------------------------------------------------------------- |Expression is a type class for values over which proofs--- may be constructed.-class (Eq ex) => Expression ex where- -- |Is expression true in all interpretations?- -- If so, then its truth is assumed without justification.- isValid :: ex -> Bool----------------------------------------------------------------- Formula: a named expression----------------------------------------------------------------- |A Formula is a named expression.-data Formula ex = Formula- { formName :: ScopedName -- ^ Name used for formula in proof chain- , formExpr :: ex -- ^ Named formula value- } deriving Show---- |Define equality of formulae as equality of formula names-instance Eq (Formula ex) where- f1 == f2 = formName f1 == formName f2---- |Define ordering of formulae based on formula names-instance Ord (Formula ex) where- f1 <= f2 = formName f1 <= formName f2--instance LookupEntryClass (Formula ex) ScopedName (Formula ex)- where- newEntry (_,form) = form- keyVal form = (formName form, form)---- | The namespace @http:\/\/id.ninebynine.org\/2003\/Ruleset\/null@-nullScope :: Namespace-nullScope = makeNamespace (Just "null") nullScopeURI--nullSN :: T.Text -> ScopedName-nullSN = makeScopedName (Just "null") nullScopeURI--tU :: String -> URI-tU = fromJust . parseURI--nullScopeURI :: URI-nullScopeURI = tU "http://id.ninebynine.org/2003/Ruleset/null"---- | The null formula.-nullFormula :: Formula ex-nullFormula = Formula- { formName = makeScopedName (Just "null") nullScopeURI "nullFormula"- , formExpr = error "Null formula"- }---- testf1 = Formula "f1" ('f',1)--- testf2 = Formula "f2" ('f',2)---- |Return a displayable form of a list of labelled formulae-showsFormulae :: - (ShowM ex) - => String -- ^ newline- -> [Formula ex] -- ^ the formulae to show- -> String -- ^ text to be placed after the formulae- -> ShowS-showsFormulae _ [] _ = id-showsFormulae newline [f] after = showsFormula newline f .- showString after-showsFormulae newline (f:fs) after = showsFormula newline f .- showString newline .- showsFormulae newline fs after---- |Create a displayable form of a labelled formula-showsFormula :: - (ShowM ex) - => String -- ^ newline- -> Formula ex -- ^ formula- -> ShowS-showsFormula newline f =- showsWidth 16 ("["++show (formName f)++"] ") .- showms (newline ++ replicate 16 ' ') (formExpr f)----------------------------------------------------------------- Rule----------------------------------------------------------------- |Rule is a data type for inference rules that can be used--- to construct a step in a proof.-data Rule ex = Rule- {- -- |Name of rule, for use when displaying a proof- ruleName :: ScopedName,- - -- |Forward application of a rule, takes a list of- -- expressions and returns a list (possibly empty)- -- of forward applications of the rule to combinations- -- of the antecedent expressions.- -- Note that all of the results returned can be assumed to- -- be (simultaneously) true, given the antecedents provided.- fwdApply :: [ex] -> [ex],- - -- |Backward application of a rule, takes an expression- -- and returns a list of alternative antecedents, each of- -- which is a list of expressions that jointly yield the- -- given consequence through application of the inference- -- rule. An empty list is returned if no antecedents- -- will allow the consequence to be inferred.- bwdApply :: ex -> [[ex]],- - -- |Inference check. Takes a list of antecedent expressions- -- and a consequent expression, returning True if the- -- consequence can be obtained from the antecedents by- -- application of the rule. When the antecedents and- -- consequent are both given, this is generally more efficient- -- that using either forward or backward chaining.- -- Also, a particular rule may not fully support either- -- forward or backward chaining, but all rules are required- -- to fully support this function.- --- -- A default implementation based on forward chaining is- -- given below.- checkInference :: [ex] -> ex -> Bool - }---- |Define equality of rules as equality of rule names-instance Eq (Rule ex) where- r1 == r2 = ruleName r1 == ruleName r2---- |Define ordering of rules based on rule names-instance Ord (Rule ex) where- r1 <= r2 = ruleName r1 <= ruleName r2--instance Show (Rule ex) where- show rl = "Rule "++show (ruleName rl)--instance LookupEntryClass (Rule ex) ScopedName (Rule ex)- where- newEntry (_,rule) = rule- keyVal rule = (ruleName rule, rule)--type RuleMap ex = LookupMap (Rule ex)---- | Checks that consequence is a result of--- applying the rule to the antecedants.-fwdCheckInference :: - (Eq ex) - => Rule ex -- ^ rule- -> [ex] -- ^ antecedants- -> ex -- ^ consequence- -> Bool-fwdCheckInference rule ante cons =- cons `elem` fwdApply rule ante--bwdCheckInference ::- (Eq ex) - => Rule ex -- ^ rule- -> [ex] -- ^ antecedants- -> ex -- ^ consequence- -> Bool-bwdCheckInference rule ante cons = any checkAnts (bwdApply rule cons)- where- checkAnts = all (`elem` ante)---- | The null rule.-nullRule :: Rule ex-nullRule = Rule- { ruleName = makeScopedName (Just "null") nullScopeURI "nullRule"- , fwdApply = \ _ -> []- , bwdApply = \ _ -> []- , checkInference = \ _ _ -> False- }----------------------------------------------------------------- Shows formatting support functions---------------------------------------------------------------- |Show a string left justified in a field of at least the specified--- number of characters width.-showsWidth :: Int -> String -> ShowS-showsWidth wid str more = str++replicate pad ' '++more- where- pad = wid - length str----------------------------------------------------------------------------------------- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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/RDF/Ruleset.hs
@@ -1,153 +0,0 @@-{-# LANGUAGE MultiParamTypeClasses #-}------------------------------------------------------------------------------------ See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : Ruleset--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke--- License : GPL V2------ Maintainer : Douglas Burke--- Stability : experimental--- Portability : MultiParamTypeClasses------ This module defines a ruleset data type, used to collect information--- about a ruleset that may contribute torwards inferences in RDF;--- e.g. RDF and RDFS are rulesets.------ A ruleset consists of a namespace, a collection of axioms and--- a collection of rules.--------------------------------------------------------------------------------------module Swish.RDF.Ruleset- ( Ruleset(..), RulesetMap- , makeRuleset, getRulesetNamespace, getRulesetAxioms, getRulesetRules- , getRulesetAxiom, getRulesetRule- , getContextAxiom, getMaybeContextAxiom- , getContextRule, getMaybeContextRule- )-where--import Swish.Utils.Namespace (Namespace, ScopedName)-import Swish.RDF.Rule (Formula(..), Rule(..))--import Swish.Utils.LookupMap- ( LookupEntryClass(..), LookupMap(..)- , mapFindMaybe- )--{--Used for the Show instance of Ruleset, which was-used for debugging but has been removed as not-really needed by the general user.--import Swish.Utils.ShowM (ShowM(..))-import Data.List (intercalate)--}--import Data.Maybe (fromMaybe, listToMaybe, mapMaybe)---- | Ruleset, having namespace, axioms and rules--data Ruleset ex = Ruleset- { rsNamespace :: Namespace- , rsAxioms :: [Formula ex]- , rsRules :: [Rule ex]- }--{---Used for debugging.--instance (ShowM ex) => Show (Ruleset ex) where- show (Ruleset ns axs rls) = - intercalate "\n" - [ "Ruleset: " ++ show ns- , "Axioms:" ]- ++ (showsFormulae "\n" axs - (intercalate "\n" ("Rules:" : map show rls))) ""--}--instance Eq (Ruleset ex) where- r1 == r2 = rsNamespace r1 == rsNamespace r2--instance LookupEntryClass (Ruleset ex) Namespace (Ruleset ex)- where- keyVal r@(Ruleset k _ _) = (k,r)- newEntry (_,r) = r--type RulesetMap ex = LookupMap (Ruleset ex)--makeRuleset :: Namespace -> [Formula ex] -> [Rule ex] -> Ruleset ex-makeRuleset nsp fms rls = Ruleset- { rsNamespace = nsp- , rsAxioms = fms- , rsRules = rls- }--getRulesetNamespace :: Ruleset ex -> Namespace-getRulesetNamespace = rsNamespace--getRulesetAxioms :: Ruleset ex -> [Formula ex]-getRulesetAxioms = rsAxioms--getRulesetRules :: Ruleset ex -> [Rule ex]-getRulesetRules = rsRules---- | Find a named axiom in a ruleset.-getRulesetAxiom :: ScopedName -> Ruleset ex -> Maybe (Formula ex)-getRulesetAxiom nam rset =- mapFindMaybe nam (LookupMap (getRulesetAxioms rset))- -- listToMaybe $ filter ( (matchName nam) . formName ) $ getRulesetAxioms rset---- | Find a named rule in a ruleset. -getRulesetRule :: ScopedName -> Ruleset ex -> Maybe (Rule ex)-getRulesetRule nam rset =- mapFindMaybe nam (LookupMap (getRulesetRules rset))- -- listToMaybe $ filter ( (matchName nam) . ruleName ) $ getRulesetRules rset---- | Find a named axiom or rule in a proof context.-getContextAxiom :: ScopedName -> Formula ex -> [Ruleset ex] -> Formula ex-getContextAxiom nam def rsets = fromMaybe def (getMaybeContextAxiom nam rsets)- {-- foldr (flip fromMaybe) def $ map (getRulesetAxiom nam) rsets- -}--getMaybeContextAxiom :: ScopedName -> [Ruleset ex] -> Maybe (Formula ex)-getMaybeContextAxiom nam rsets =- listToMaybe $ mapMaybe (getRulesetAxiom nam) rsets--getContextRule :: ScopedName -> Rule ex -> [Ruleset ex] -> Rule ex-getContextRule nam def rsets = fromMaybe def (getMaybeContextRule nam rsets)- {-- foldr (flip fromMaybe) def $ map (getRulesetRule nam) rsets- -}--getMaybeContextRule :: ScopedName -> [Ruleset ex] -> Maybe (Rule ex)-getMaybeContextRule nam rsets =- listToMaybe $ mapMaybe (getRulesetRule nam) rsets---------------------------------------------------------------------------------------- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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/RDF/SwishCommands.hs
@@ -1,422 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}------------------------------------------------------------------------------------- See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : SwishCommands--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke--- License : GPL V2------ Maintainer : Douglas Burke--- Stability : experimental--- Portability : OverloadedStrings------ SwishCommands: functions to deal with indivudual Swish command options.--------------------------------------------------------------------------------------module Swish.RDF.SwishCommands- ( swishFormat- , swishBase- -- , swishVerbose- , swishInput- , swishOutput- , swishMerge- , swishCompare- , swishGraphDiff- , swishScript- )-where--import Swish.RDF.SwishMonad- ( SwishStateIO, SwishState(..), SwishStatus(..)- , setFormat, setBase, setGraph- , resetInfo, resetError, setStatus- -- , setVerbose- , SwishFormat(..)- , swishError- , reportLine- )--import Swish.RDF.SwishScript (parseScriptFromText)--import Swish.RDF.GraphPartition- ( GraphPartition(..)- , partitionGraph, comparePartitions- , partitionShowP- )--import Swish.RDF.RDFGraph- ( RDFGraph, merge )--import qualified Swish.RDF.TurtleFormatter as TTLF-import qualified Swish.RDF.N3Formatter as N3F-import qualified Swish.RDF.NTFormatter as NTF--import Swish.RDF.TurtleParser (parseTurtle)-import Swish.RDF.N3Parser (parseN3)-import Swish.RDF.NTParser (parseNT)-import Swish.RDF.RDFParser (appendURIs)--import Swish.RDF.GraphClass- ( LDGraph(..)- , Label(..)- )--import Swish.Utils.QName (QName, qnameFromURI, qnameFromFilePath, getQNameURI)--import System.IO- ( Handle, openFile, IOMode(..)- , hPutStr, hPutStrLn, hClose- , hIsReadable, hIsWritable- , stdin, stdout- )--import Network.URI (parseURIReference)--import Control.Monad.Trans (MonadTrans(..))-import Control.Monad.State (modify, gets)-import Control.Monad (liftM, when)--import qualified Data.Text.Lazy as T-import qualified Data.Text.Lazy.IO as IO-import System.IO.Error--import Data.Maybe (isJust, fromMaybe)----------------------------------------------------------------- Set file format to supplied value----------------------------------------------------------------- the second argument allows for options to be passed along--- with the format (a la cwm) but this is not supported yet----swishFormat :: SwishFormat -> Maybe String -> SwishStateIO ()-swishFormat fmt _ = modify (setFormat fmt)----------------------------------------------------------------- Set base URI to supplied value----------------------------------------------------------------- the Maybe String argument is ignored (a result of a lack of--- design with the command-line processing)----swishBase :: Maybe QName -> Maybe String -> SwishStateIO ()-swishBase mb _ = modify (setBase mb)----------------------------------------------------------------- Read graph from named file---------------------------------------------------------------swishInput :: Maybe String -> SwishStateIO ()-swishInput fnam =- swishReadGraph fnam >>= maybe (return ()) (modify . setGraph)- ---------------------------------------------------------------- Merge graph from named file---------------------------------------------------------------swishMerge :: Maybe String -> 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)----------------------------------------------------------------- Compare graph from named file---------------------------------------------------------------swishCompare :: Maybe String -> 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---------------------------------------------------------------swishGraphDiff :: Maybe String -> SwishStateIO ()-swishGraphDiff fnam =- swishReadGraph fnam >>= maybe (return ()) diffGraph--diffGraph :: RDFGraph -> SwishStateIO ()-diffGraph gr = do- oldGr <- gets graph- let p1 = partitionGraph (getArcs oldGr)- p2 = partitionGraph (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---------------------------------------------------------------swishScript :: Maybe String -> 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 = fromMaybe defURI `liftM` gets 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 $ 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 ()----------------------------------------------------------------- Output graph to named file---------------------------------------------------------------swishOutput :: Maybe String -> 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 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 <- lift $ try $ openFile 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 <- lift $ try $ openFile 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 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/RDF/SwishMain.hs
@@ -1,263 +0,0 @@------------------------------------------------------------------------------------ See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : SwishMain--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke--- License : GPL V2------ Maintainer : Douglas Burke--- Stability : experimental--- Portability : H98------ 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).------ This module is likely to be moved to the Swish module.---------------------------------------------------------------------------------------- TODO:------ * Add RDF/XML input and output-----module Swish.RDF.SwishMain (- SwishStatus(..), SwishAction,- runSwish,- runSwishActions,- displaySwishHelp,- splitArguments,- validateCommands- ) where--import Swish.RDF.SwishCommands- ( swishFormat- , swishBase- , swishInput- , swishOutput- , swishMerge- , swishCompare- , swishGraphDiff- , swishScript- )--import Swish.RDF.SwishMonad- ( SwishStateIO, SwishState(..), SwishStatus(..)- , emptyState- , SwishFormat(..)- )--import Swish.Utils.QName (qnameFromURI)-import Swish.Utils.ListHelpers (breakAll)--import Control.Monad.State (execStateT)-import Control.Monad (liftM)--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.--- -newtype SwishAction = SA (SwishStateIO ())---- | 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 $ f marg- in case nam of- "-ttl" -> wrap $ swishFormat Turtle- "-nt" -> wrap $ swishFormat NT- "-n3" -> wrap $ swishFormat N3- "-i" -> wrap swishInput- "-m" -> wrap swishMerge- "-c" -> wrap swishCompare- "-d" -> wrap swishGraphDiff- "-o" -> wrap swishOutput- "-b" -> validateBase 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 :: Maybe String -> Either (String, SwishStatus) SwishAction-validateBase Nothing = Right $ SA $ swishBase Nothing Nothing-validateBase (Just b) =- case fmap qnameFromURI (parseURI b) of- j@(Just _) -> Right $ SA $ swishBase j Nothing- _ -> 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---- | Execute the given set of actions.-runSwishActions :: [SwishAction] -> IO SwishStatus-runSwishActions acts = exitcode `liftM` execStateT (swishCommands acts) emptyState---------------------------------------------------------------------------------------- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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/RDF/SwishMonad.hs
@@ -1,270 +0,0 @@-{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-}------------------------------------------------------------------------------------ See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : SwishMonad--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke--- License : GPL V2------ Maintainer : Douglas Burke--- Stability : experimental--- Portability : FlexibleInstances, MultiParamTypeClasses------ Composed state and IO monad for Swish--------------------------------------------------------------------------------------module Swish.RDF.SwishMonad- ( SwishStateIO, SwishState(..), SwishStatus(..)- , setFormat, setBase, setGraph- , modGraphs, findGraph, findFormula- , modRules, findRule- , modRulesets, findRuleset- , findOpenVarModify, findDatatype- , setInfo, resetInfo, setError, resetError- , setStatus- -- , setVerbose- , emptyState- , SwishFormat(..)- , NamedGraph(..), NamedGraphMap- , swishError- , reportLines, reportLine- )-where--import Swish.RDF.RDFGraph- ( RDFGraph, emptyRDFGraph )--import Swish.RDF.RDFRuleset- ( RDFFormula, RDFRule, RDFRuleMap, RDFRuleset, RDFRulesetMap )--import Swish.RDF.RDFDatatype- ( RDFDatatype )--import Swish.RDF.RDFVarBinding- ( RDFOpenVarBindingModify- )--import Swish.RDF.BuiltInMap- ( findRDFOpenVarBindingModifier- , findRDFDatatype- , rdfRulesetMap- )--import Swish.RDF.Ruleset- ( getMaybeContextAxiom- , getMaybeContextRule- )--import Swish.RDF.Rule- ( Formula(..)- )--import Swish.Utils.Namespace (ScopedName, getScopeNamespace)-import Swish.Utils.QName (QName)--import Swish.Utils.LookupMap- ( LookupEntryClass(..), LookupMap(..)- , emptyLookupMap- , mapFindMaybe- , mapVals- )--import Control.Monad.Trans (MonadTrans(..))-import Control.Monad.State (StateT(..), modify)--import System.IO (hPutStrLn, stderr)--{-|-The supported input and output formats.--}-data SwishFormat = - Turtle -- ^ Turtle format- | N3 -- ^ N3 format- | NT -- ^ NTriples format- deriving 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 message- }---- | 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 (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."--type SwishStateIO a = StateT SwishState IO a--emptyState :: SwishState-emptyState = SwishState- { format = N3- , base = Nothing- , graph = emptyRDFGraph- , graphs = emptyLookupMap- , rules = emptyLookupMap- , rulesets = rdfRulesetMap- , infomsg = Nothing- , errormsg = Nothing- , exitcode = SwishSuccess- }--setFormat :: SwishFormat -> SwishState -> SwishState-setFormat fm state = state { format = fm }--setBase :: Maybe QName -> SwishState -> SwishState-setBase bs state = state { base = bs }--setGraph :: RDFGraph -> SwishState -> SwishState-setGraph gr state = state { graph = gr }--modGraphs ::- ( NamedGraphMap -> NamedGraphMap ) -> SwishState -> SwishState-modGraphs grmod state = state { graphs = grmod (graphs state) }--findGraph :: ScopedName -> SwishState -> Maybe [RDFGraph]-findGraph nam state = mapFindMaybe nam (graphs state)--findFormula :: ScopedName -> SwishState -> Maybe RDFFormula-findFormula nam state = case findGraph nam state of- Nothing -> getMaybeContextAxiom nam (mapVals $ rulesets state)- Just [] -> Just $ Formula nam emptyRDFGraph- Just grs -> Just $ Formula nam (head grs)--modRules ::- ( RDFRuleMap -> RDFRuleMap ) -> SwishState -> SwishState-modRules rlmod state = state { rules = rlmod (rules state) }--findRule :: ScopedName -> SwishState -> Maybe RDFRule-findRule nam state =- let- localrule = mapFindMaybe nam (rules state)- contextrule = getMaybeContextRule nam $ mapVals $ rulesets state- in- case localrule of- Nothing -> contextrule- justlr -> justlr--modRulesets ::- ( RDFRulesetMap -> RDFRulesetMap ) -> SwishState -> SwishState-modRulesets rsmod state = state { rulesets = rsmod (rulesets state) }--findRuleset ::- ScopedName -> SwishState -> Maybe RDFRuleset-findRuleset nam state = mapFindMaybe (getScopeNamespace nam) (rulesets state)--findOpenVarModify :: ScopedName -> SwishState -> Maybe RDFOpenVarBindingModify-findOpenVarModify nam _ = findRDFOpenVarBindingModifier nam--findDatatype :: ScopedName -> SwishState -> Maybe RDFDatatype-findDatatype nam _ = findRDFDatatype nam--setInfo :: String -> SwishState -> SwishState-setInfo msg state = state { infomsg = Just msg }--resetInfo :: SwishState -> SwishState-resetInfo state = state { infomsg = Nothing }--setError :: String -> SwishState -> SwishState-setError msg state = state { errormsg = Just msg }--resetError :: SwishState -> SwishState-resetError state = state { errormsg = Nothing }--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]- }--instance LookupEntryClass NamedGraph ScopedName [RDFGraph]- where- keyVal (NamedGraph k v) = (k,v)- newEntry (k,v) = NamedGraph k v--type NamedGraphMap = LookupMap NamedGraph---- | Report error and set exit status code--swishError :: String -> SwishStatus -> SwishStateIO ()-swishError msg sts = do- reportLines [msg, show sts ++ "\n"]- -- when (sts == 4) $ reportLine "Use 'Swish -h' or 'Swish -?' for help\n"- modify $ setStatus sts---- | Output text to the standard error stream------ Each string in the supplied list is a line of text to--- be displayed.--reportLines :: [String] -> SwishStateIO ()-reportLines = mapM_ reportLine --reportLine :: String -> SwishStateIO ()-reportLine line =- -- lift putStrLn line- lift $ hPutStrLn stderr line---------------------------------------------------------------------------------------- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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/RDF/SwishScript.hs
@@ -1,1512 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}------------------------------------------------------------------------------------ See end of this file for licence information.----------------------------------------------------------------------------------{- |-Module : SwishScript-Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke-License : GPL V2--Maintainer : Douglas Burke-Stability : experimental-Portability : OverloadedStrings--This module implements the Swish script processor: it parses a script-from a supplied string, and returns a list of Swish state transformer-functions whose effect, when applied to a state value, is to implement-the supplied script.---}--module Swish.RDF.SwishScript- ( - -- * Syntax- -- $syntax- - -- ** Defining a prefix- -- $prefixLine- - -- ** Naming a graph- -- $nameItem- - -- ** Reading and writing graphs- - -- $readGraph- - -- $writeGraph- - -- ** Merging graphs- -- $mergeGraphs- - -- ** Comparing graphs- - -- $compareGraphs- - -- $assertEquiv- - -- $assertMember- - -- ** Defining rules- - -- $defineRule- - -- $defineRuleset- - -- $defineConstraints- - -- ** Apply a rule- -- $fwdChain- - -- $bwdChain- - -- ** Define a proof- -- $proof- - -- * An example script- -- $exampleScript- - -- * Parsing- - parseScriptFromText - )-where--import Swish.RDF.SwishMonad- ( SwishStateIO, SwishStatus(..) - , modGraphs, findGraph, findFormula- , modRules, findRule- , modRulesets, findRuleset- , findOpenVarModify, findDatatype- , setInfo, setError, setStatus- , NamedGraph(..)- )--import Swish.RDF.RDFDatatype- ( RDFDatatype )--import Swish.RDF.RDFRuleset- ( RDFFormula, RDFRule- , RDFRuleset- , makeRDFClosureRule- )--import Swish.RDF.RDFProof- ( RDFProofStep, makeRDFProof, makeRDFProofStep )--import Swish.RDF.RDFVarBinding- ( RDFVarBindingModify- )--import Swish.RDF.RDFGraphShowM()--import Swish.RDF.RDFGraph- ( RDFGraph, RDFLabel(..)- , emptyRDFGraph- , NamespaceMap- , setNamespaces- , merge, add- )--import Swish.RDF.RDFParser (whiteSpace, lexeme, symbol, eoln, manyTill)--import Swish.RDF.N3Parser- ( parseAnyfromText- , parseN3 - , N3Parser, N3State(..)- , getPrefix- , subgraph- , n3symbol -- was uriRef2,- , quickVariable -- was varid- , lexUriRef- , newBlankNode- )--import Swish.RDF.N3Formatter (formatGraphAsBuilder)-import Swish.RDF.Datatype (typeMkRules)-import Swish.RDF.Proof (explainProof, showsProof)-import Swish.RDF.Ruleset (makeRuleset, getRulesetRule, getMaybeContextRule)-import Swish.RDF.Rule (Formula(..), Rule(..)) -import Swish.RDF.VarBinding (composeSequence)--import Swish.Utils.Namespace (ScopedName, getScopeNamespace)-import Swish.Utils.QName (QName, qnameFromURI)-import Swish.Utils.LookupMap (mapReplaceOrAdd)-import Swish.Utils.ListHelpers (equiv, flist)--import qualified Data.Text.Lazy as L-import qualified Data.Text.Lazy.Builder as B-import qualified Data.Text.Lazy.IO as LIO-import Text.ParserCombinators.Poly.StateText--import Control.Monad (unless, when, liftM)-import Control.Monad.State (modify, gets, lift)--import Network.URI (URI(..))--import Data.Monoid (Monoid(..))--import qualified System.IO.Error as IO-------------------------------------------------------------------- The parser used to be based on the Notation3 parser, and used many--- of the same syntax productions, but the top-level productions used--- are quite different. With the parser re-write it's less clear--- what is going on.------ NOTE: during the parser re-write we strip out some of this functionality--- ---- | Parser for Swish script processor-parseScriptFromText :: - Maybe QName -- ^ Default base for the script- -> L.Text -- ^ Swish script- -> Either String [SwishStateIO ()]-parseScriptFromText = parseAnyfromText script --------------------------------------------------------------------------- Syntax productions-------------------------------------------------------------------------between :: Parser s lbr -> Parser s rbr -> Parser s a -> Parser s a-between = bracket--n3SymLex :: N3Parser ScopedName-n3SymLex = lexeme n3symbol--setTo :: N3Parser ()-setTo = isymbol ":-"--semicolon :: N3Parser ()-semicolon = isymbol ";"--comma :: N3Parser ()-comma = isymbol ","--commentText :: N3Parser String-commentText = semicolon *> restOfLine--script :: N3Parser [SwishStateIO ()]-script = do- whiteSpace- scs <- many command- eof- return scs--isymbol :: String -> N3Parser ()-isymbol s = symbol s >> return ()--command :: N3Parser (SwishStateIO ())-command =- prefixLine- <|> nameItem- <|> readGraph- <|> writeGraph- <|> mergeGraphs- <|> compareGraphs- <|> assertEquiv- <|> assertMember- <|> defineRule- <|> defineRuleset- <|> defineConstraints- <|> checkProofCmd- <|> fwdChain- <|> bwdChain--prefixLine :: N3Parser (SwishStateIO ())-prefixLine = do- -- try $ isymbol "@prefix"- isymbol "@prefix"- getPrefix- whiteSpace- isymbol "."- return $ return ()---- name :- graph--- name :- ( graph* )-nameItem :: N3Parser (SwishStateIO ())-nameItem = - ssAddGraph <$> n3SymLex <*> (symbol ":-" *> graphOrList)- -maybeURI :: N3Parser (Maybe URI)-maybeURI = (Just <$> lexUriRef) <|> return Nothing---- @read name [ <uri> ]-readGraph :: N3Parser (SwishStateIO ())-readGraph = commandName "@read" *> (ssRead <$> n3SymLex <*> maybeURI)---- @write name [ <uri> ] ; Comment-writeGraph :: N3Parser (SwishStateIO ())-writeGraph =- do { commandName "@write"- ; n <- n3SymLex- ; let gs = ssGetList n :: SwishStateIO (Either String [RDFGraph])- ; muri <- maybeURI- ; c <- commentText- ; return $ ssWriteList muri gs c- }---- @merge ( name* ) => name-mergeGraphs :: N3Parser (SwishStateIO ())-mergeGraphs = do- commandName "@merge"- gs <- graphList- isymbol "=>"- n <- n3SymLex- return $ ssMerge n gs---- @compare name name-compareGraphs :: N3Parser (SwishStateIO ())-compareGraphs =- commandName "@compare" *> (ssCompare <$> n3SymLex <*> n3SymLex)- --- @<command> name name ; Comment-assertArgs :: (ScopedName -> ScopedName -> String -> SwishStateIO ())- -> String -> N3Parser (SwishStateIO ())-assertArgs assertFunc cName = do- commandName $ '@':cName- assertFunc <$> n3SymLex <*> n3SymLex <*> commentText- --- @asserteq name name ; Comment-assertEquiv :: N3Parser (SwishStateIO ())-assertEquiv = assertArgs ssAssertEq "asserteq" - --- @assertin name name ; Comment -assertMember :: N3Parser (SwishStateIO ())-assertMember = assertArgs ssAssertIn "assertin"- --- @rule name :- ( name* ) => name [ | ( (name var*)* ) ] -defineRule :: N3Parser (SwishStateIO ())-defineRule =- do { commandName "@rule"- ; rn <- n3SymLex- ; setTo- ; ags <- graphOrList- ; isymbol "=>"- ; cg <- graphExpr- ; vms <- varModifiers <|> pure []- ; return $ ssDefineRule rn ags cg vms- }---- @ruleset name :- ( name* ) ; ( name* )-defineRuleset :: N3Parser (SwishStateIO ())-defineRuleset =- commandName "@ruleset" *> - (ssDefineRuleset <$> n3SymLex <*> (setTo *> nameList) <*> (semicolon *> nameList))- --- @constraints pref :- ( name* ) | ( name* )-defineConstraints :: N3Parser (SwishStateIO ())-defineConstraints =- commandName "@constraints" *> - (ssDefineConstraints <$> n3SymLex <*> (setTo *> graphOrList) <*> (symbol "|" *> nameOrList))- --- @proof name ( name* )--- @input name--- @step name ( name* ) => name # rule-name, antecedents, consequent--- @result name-checkProofCmd :: N3Parser (SwishStateIO ())-checkProofCmd =- do { commandName "@proof"- ; pn <- n3SymLex- ; sns <- nameList- ; commandName "@input"- ; igf <- formulaExpr- ; sts <- many checkStep- ; commandName "@result"- ; rgf <- formulaExpr- ; return $ ssCheckProof pn sns igf sts rgf- }--checkStep ::- N3Parser (Either String [RDFRuleset]- -> SwishStateIO (Either String RDFProofStep))-checkStep =- commandName "@step" *> - (ssCheckStep <$> n3SymLex <*> formulaList <*> (symbol "=>" *> formulaExpr))---- # ruleset rule (antecedents) => result--- @fwdchain pref name ( name* ) => name-fwdChain :: N3Parser (SwishStateIO ())-fwdChain =- do { commandName "@fwdchain"- ; sn <- n3SymLex- ; rn <- n3SymLex- ; ags <- graphOrList- ; isymbol "=>"- ; cn <- n3SymLex- ; s <- stGet- ; let prefs = prefixUris s- ; return $ ssFwdChain sn rn ags cn prefs- }---- # ruleset rule consequent <= (antecedent-alts)--- @bwdchain pref name graph <= name-bwdChain :: N3Parser (SwishStateIO ())-bwdChain =- do { commandName "@bwdchain"- ; sn <- n3SymLex- ; rn <- n3SymLex- ; cg <- graphExpr- ; isymbol "<="- ; an <- n3SymLex- ; s <- stGet- ; let prefs = prefixUris s- ; return $ ssBwdChain sn rn cg an prefs- }--------------------------------------------------------------------------- Syntax clause helpers--------------------------------------------------------------------------- TODO: is the loss of identLetter a problem?-commandName :: String -> N3Parser ()--- commandName cmd = try (string cmd *> notFollowedBy identLetter *> whiteSpace)-commandName cmd = symbol cmd *> pure ()--restOfLine :: N3Parser String-restOfLine = manyTill (satisfy (const True)) eoln <* whiteSpace- -br :: N3Parser a -> N3Parser a-br = between (symbol "(") (symbol ")")--nameList :: N3Parser [ScopedName]-nameList = br $ many n3SymLex- -toList :: a -> [a]-toList = (:[])- -nameOrList :: N3Parser [ScopedName]-nameOrList =- (toList <$> n3SymLex) - <|> nameList- -graphExpr :: N3Parser (SwishStateIO (Either String RDFGraph))-graphExpr =- graphOnly- <|>- do { f <- formulaExpr- ; return $ liftM (liftM formExpr) f- }--graphOnly :: N3Parser (SwishStateIO (Either String RDFGraph))-graphOnly =- do { isymbol "{"- ; b <- newBlankNode- ; g <- subgraph b- ; isymbol "}"- ; s <- stGet- ; let gp = setNamespaces (prefixUris s) g- ; return $ return (Right gp)- }--graphList :: N3Parser [SwishStateIO (Either String RDFGraph)]-graphList = br (many graphExpr)--graphOrList :: N3Parser [SwishStateIO (Either String RDFGraph)]-graphOrList =- (toList <$> graphExpr)- <|> graphList--formulaExpr :: N3Parser (SwishStateIO (Either String RDFFormula))-formulaExpr = n3SymLex >>= namedGraph--namedGraph :: ScopedName -> N3Parser (SwishStateIO (Either String RDFFormula))-namedGraph n =- (ssAddReturnFormula n <$> (setTo *> graphOnly))- <|> return (ssGetFormula n)--formulaList :: N3Parser [SwishStateIO (Either String RDFFormula)]-formulaList = between (symbol "(") (symbol ")") (many formulaExpr)--varModifiers :: N3Parser [(ScopedName,[RDFLabel])]-varModifiers = symbol "|" *> varModList--varModList :: N3Parser [(ScopedName,[RDFLabel])]-varModList = - br (sepBy varMod comma)- <|> toList <$> lexeme varMod--varMod :: N3Parser (ScopedName,[RDFLabel])-varMod = (,) <$> n3SymLex <*> many (lexeme quickVariable)--------------------------------------------------------------------------- SwishState helper functions----------------------------------------------------------------------------- The functions below operate in the SwishStateIO monad, and are used--- to assemble an executable version of the parsed script.---- | Return a message to the user. At present the message begins with '# '--- but this may be removed.----ssReport :: - String -- ^ message contents- -> SwishStateIO ()--- ssReport msg = lift $ putStrLn $ "# " ++ msg-ssReport msg = modify $ setInfo $ "# " ++ msg--ssReportLabel :: - String -- ^ label for the message- -> String -- ^ message contents- -> SwishStateIO ()-ssReportLabel lbl msg = ssReport $ lbl ++ ": " ++ msg--ssAddReturnFormula ::- ScopedName -> SwishStateIO (Either String RDFGraph)- -> SwishStateIO (Either String RDFFormula)-ssAddReturnFormula nam gf =- do { egr <- gf- ; ssAddGraph nam [return egr]- ; return $ liftM (Formula nam) egr- }--ssAddGraph ::- ScopedName -> [SwishStateIO (Either String RDFGraph)]- -> SwishStateIO ()-ssAddGraph nam gf =- let errmsg = "Graph/list not added: "++show nam++"; "- in- do { esg <- sequence gf -- [Either String RDFGraph]- ; let egs = sequence esg -- Either String [RDFGraph]- ; let fgs = case egs of- Left er -> setError (errmsg++er)- Right gs -> modGraphs (mapReplaceOrAdd (NamedGraph nam gs))- ; modify fgs- }--ssGetGraph :: ScopedName -> SwishStateIO (Either String RDFGraph)-ssGetGraph nam = liftM head <$> ssGetList nam- -ssGetFormula :: ScopedName -> SwishStateIO (Either String RDFFormula)-ssGetFormula nam = gets find- where- find st = case findFormula nam st of- Nothing -> Left ("Formula not present: "++show nam)- Just gr -> Right gr--ssGetList :: ScopedName -> SwishStateIO (Either String [RDFGraph])-ssGetList nam = gets find- where- find st = case findGraph nam st of- Nothing -> Left ("Graph or list not present: "++show nam)- Just grs -> Right grs--ssRead :: ScopedName -> Maybe URI -> SwishStateIO ()-ssRead nam muri = ssAddGraph nam [ssReadGraph muri]--ssReadGraph :: Maybe URI -> SwishStateIO (Either String RDFGraph)-ssReadGraph muri = - let gf inp = case inp of- Left es -> Left es- Right is -> parseN3 is (fmap qnameFromURI muri)- - in gf `liftM` getResourceData muri--ssWriteList ::- Maybe URI -> SwishStateIO (Either String [RDFGraph]) -> String- -> SwishStateIO ()-ssWriteList muri gf comment = do- esgs <- gf- case esgs of- Left er -> modify $ setError ("Cannot write list: "++er)- Right [] -> putResourceData Nothing (B.fromLazyText (L.concat ["# ", L.pack comment, "\n+ Swish: Writing empty list"]))- Right [gr] -> ssWriteGraph muri gr comment- Right grs -> mapM_ writegr (zip [(0::Int)..] grs)- where- writegr (n,gr) = ssWriteGraph (murin muri n) gr- ("["++show n++"] "++comment)- murin Nothing _ = Nothing- murin (Just uri) n = - let rp = reverse $ uriPath uri- (rLastSet, rRest) = break (=='/') rp- (before, after) = break (=='.') $ reverse rLastSet- newPath = reverse rRest ++ "/" ++ before ++ show n ++ after- in case rLastSet of- "" -> error $ "Invalid URI (path ends in /): " ++ show uri- _ -> Just $ uri { uriPath = newPath }- - --{--ssWrite ::- Maybe String -> SwishStateIO (Either String RDFGraph) -> String- -> SwishStateIO ()-ssWrite muri gf comment =- do { esg <- gf- ; case esg of- Left er -> modify $ setError ("Cannot write graph: "++er)- Right gr -> ssWriteGraph muri gr comment- }--}--ssWriteGraph :: Maybe URI -> RDFGraph -> String -> SwishStateIO ()-ssWriteGraph muri gr comment =- putResourceData muri (c `mappend` formatGraphAsBuilder gr)- where- c = B.fromLazyText $ L.concat ["# ", L.pack comment, "\n"]--ssMerge ::- ScopedName -> [SwishStateIO (Either String RDFGraph)]- -> SwishStateIO ()-ssMerge nam gfs =- let errmsg = "Graph merge not defined: "++show nam++"; "- in- do { ssReportLabel "Merge" (show nam)- ; esg <- sequence gfs -- [Either String RDFGraph]- ; let egs = sequence esg -- Either String [RDFGraph]- ; let fgs = case egs of- Left er -> setError (errmsg++er)- Right [] -> setError (errmsg++"No graphs to merge")- Right gs -> modGraphs (mapReplaceOrAdd (NamedGraph nam [g]))- where g = foldl1 merge gs- ; modify fgs- }--ssCompare :: ScopedName -> ScopedName -> SwishStateIO ()-ssCompare n1 n2 =- do { ssReportLabel "Compare" (show n1 ++ " " ++ show n2)- ; g1 <- ssGetGraph n1- ; g2 <- ssGetGraph n2- ; when (g1 /= g2) (modify $ setStatus SwishGraphCompareError)- }--ssAssertEq :: ScopedName -> ScopedName -> String -> SwishStateIO ()-ssAssertEq n1 n2 comment =- let er1 = ":\n Graph or list compare not performed: invalid graph/list."- in- do { ssReportLabel "AssertEq" comment- ; g1 <- ssGetList n1- ; g2 <- ssGetList n2- ; case (g1,g2) of- (Left er,_) -> modify $ setError (comment++er1++"\n "++er)- (_,Left er) -> modify $ setError (comment++er1++"\n "++er)- (Right gr1,Right gr2) -> - unless (equiv gr1 gr2) $ modify $- setError (comment++":\n Graph "++show n1- ++" differs from "++show n2++".")- }--ssAssertIn :: ScopedName -> ScopedName -> String -> SwishStateIO ()-ssAssertIn n1 n2 comment =- let er1 = ":\n Membership test not performed: invalid graph."- er2 = ":\n Membership test not performed: invalid list."- in- do { ssReportLabel "AssertIn" comment- ; g1 <- ssGetGraph n1- ; g2 <- ssGetList n2- ; case (g1,g2) of- (Left er,_) -> modify $ setError (comment++er1++"\n "++er)- (_,Left er) -> modify $ setError (comment++er2++"\n "++er)- (Right gr,Right gs) ->- unless (gr `elem` gs) $ modify $- setError (comment++":\n Graph "++show n1- ++" not a member of "++show n2)- }---- Note: this is probably incomplete, though it should work in simple cases.--- A complete solution would have the binding modifiers subject to--- re-arrangement to suit the actual bound variables encountered.--- See VarBinding.findCompositions and VarBinding.findComposition------ This code should be adequate if variable bindings are always used--- in combinations consisting of a single modifier followed by any number--- of filters.----ssDefineRule ::- ScopedName- -> [SwishStateIO (Either String RDFGraph)]- -> SwishStateIO (Either String RDFGraph)- -> [(ScopedName,[RDFLabel])]- -> SwishStateIO ()-ssDefineRule rn agfs cgf vmds =- let errmsg1 = "Rule definition error in antecedent graph(s): "- errmsg2 = "Rule definition error in consequent graph: "- errmsg3 = "Rule definition error in variable modifier(s): "- errmsg4 = "Incompatible variable binding modifier sequence"- in- do { aesg <- sequence agfs -- [Either String RDFGraph]- ; let ags = sequence aesg :: Either String [RDFGraph]- ; cg <- cgf -- Either String RDFGraph- ; let vmfs = map ssFindVarModify vmds- ; evms <- sequence vmfs -- [Either String RDFVarBindingModify]- ; let vms = sequence evms :: Either String [RDFVarBindingModify]- ; let frl = case (ags,cg,vms) of- (Left er,_,_) -> setError (errmsg1++er)- (_,Left er,_) -> setError (errmsg2++er)- (_,_,Left er) -> setError (errmsg3++er)- (Right agrs,Right cgr,Right vbms) ->- let- newRule = makeRDFClosureRule rn agrs cgr- in- case composeSequence vbms of- Just vm -> modRules (mapReplaceOrAdd (newRule vm))- Nothing -> setError errmsg4- ; modify frl- }--ssFindVarModify ::- (ScopedName,[RDFLabel]) -> SwishStateIO (Either String RDFVarBindingModify)-ssFindVarModify (nam,lbs) = gets $ \st ->- case findOpenVarModify nam st of- Just ovbm -> Right (ovbm lbs)- Nothing -> Left ("Undefined modifier: "++show nam)--ssDefineRuleset ::- ScopedName- -> [ScopedName]- -> [ScopedName]- -> SwishStateIO ()-ssDefineRuleset sn ans rns =- let errmsg1 = "Error in ruleset axiom(s): "- errmsg2 = "Error in ruleset rule(s): "- in- do { let agfs = mapM ssGetFormula ans- :: SwishStateIO [Either String RDFFormula]- ; aesg <- agfs -- [Either String RDFFormula]- ; let eags = sequence aesg :: Either String [RDFFormula]- ; let erlf = mapM ssFindRule rns- :: SwishStateIO [Either String RDFRule]- ; rles <- erlf -- [Either String RDFRule]- ; let erls = sequence rles :: Either String [RDFRule]- ; let frs = case (eags,erls) of- (Left er,_) -> setError (errmsg1++er)- (_,Left er) -> setError (errmsg2++er)- (Right ags,Right rls) ->- modRulesets (mapReplaceOrAdd rs)- where- rs = makeRuleset (getScopeNamespace sn) ags rls- ; modify frs- }--ssFindRule :: ScopedName -> SwishStateIO (Either String RDFRule)-ssFindRule nam = gets find- where- find st = case findRule nam st of- Nothing -> Left ("Rule not found: "++show nam)- Just rl -> Right rl--ssDefineConstraints ::- ScopedName- -> [SwishStateIO (Either String RDFGraph)]- -> [ScopedName]- -> SwishStateIO ()-ssDefineConstraints sn cgfs dtns =- let errmsg1 = "Error in constraint graph(s): "- errmsg2 = "Error in datatype(s): "- in- do { cges <- sequence cgfs -- [Either String RDFGraph]- ; let ecgs = sequence cges :: Either String [RDFGraph]- ; let ecgr = case ecgs of- Left er -> Left er- Right [] -> Right emptyRDFGraph- Right grs -> Right $ foldl1 merge grs- ; edtf <- mapM ssFindDatatype dtns- -- [Either String RDFDatatype]- ; let edts = sequence edtf :: Either String [RDFDatatype]- ; let frs = case (ecgr,edts) of- (Left er,_) -> setError (errmsg1++er)- (_,Left er) -> setError (errmsg2++er)- (Right cgr,Right dts) ->- modRulesets (mapReplaceOrAdd rs)- where- rs = makeRuleset (getScopeNamespace sn) [] rls- rls = concatMap (`typeMkRules` cgr) dts- ; modify frs- }--ssFindDatatype :: ScopedName -> SwishStateIO (Either String RDFDatatype)-ssFindDatatype nam = gets find- where- find st = case findDatatype nam st of- Nothing -> Left ("Datatype not found: "++show nam)- Just dt -> Right dt---ssCheckProof ::- ScopedName -- proof name- -> [ScopedName] -- ruleset names- -> SwishStateIO (Either String RDFFormula) -- input formula- -> [Either String [RDFRuleset] -- proof step from rulesets- -> SwishStateIO (Either String RDFProofStep)]- -> SwishStateIO (Either String RDFFormula) -- result formula- -> SwishStateIO ()-ssCheckProof pn sns igf stfs rgf =- let- infmsg1 = "Proof satisfied: "- errmsg1 = "Error in proof ruleset(s): "- errmsg2 = "Error in proof input: "- errmsg3 = "Error in proof step(s): "- errmsg4 = "Error in proof goal: "- errmsg5 = "Proof not satisfied: "- proofname = " (Proof "++show pn++")"- in- do { let rs1 = map ssFindRuleset sns :: [SwishStateIO (Either String RDFRuleset)]- ; rs2 <- sequence rs1 -- [Either String RDFRuleset]- ; let erss = sequence rs2 :: Either String [RDFRuleset]- ; eig <- igf -- Either String RDFFormula- ; let st1 = sequence $ flist stfs erss :: SwishStateIO [Either String RDFProofStep]- ; st2 <- st1 -- [Either String RDFProofStep]- ; let ests = sequence st2 :: Either String [RDFProofStep]- ; erg <- rgf -- Either String RDFFormula- ; let proof = case (erss,eig,ests,erg) of- (Left er,_,_,_) -> Left (errmsg1++er++proofname)- (_,Left er,_,_) -> Left (errmsg2++er++proofname)- (_,_,Left er,_) -> Left (errmsg3++er++proofname)- (_,_,_,Left er) -> Left (errmsg4++er++proofname)- (Right rss, Right ig, Right sts, Right rg) ->- Right (makeRDFProof rss ig rg sts)- ; when False $ case proof of- (Left _) -> return ()- (Right pr) -> putResourceData Nothing $- B.fromLazyText (L.concat ["Proof ", L.pack (show pn), "\n"])- `mappend`- B.fromString (showsProof "\n" pr "\n")- -- TODO: clean up- ; let checkproof = case proof of- (Left er) -> setError er- (Right pr) ->- case explainProof pr of- Nothing -> setInfo (infmsg1++show pn)- Just ex -> setError (errmsg5++show pn++", "++ex)- {-- if not $ checkProof pr then- setError (errmsg5++show pn)- else- setInfo (infmsg1++show pn)- -}- ; modify checkproof- }--ssCheckStep ::- ScopedName -- rule name- -> [SwishStateIO (Either String RDFFormula)] -- antecedent graph formulae- -> SwishStateIO (Either String RDFFormula) -- consequent graph formula- -> Either String [RDFRuleset] -- rulesets- -> SwishStateIO (Either String RDFProofStep) -- resulting proof step-ssCheckStep _ _ _ (Left er) = return $ Left er-ssCheckStep rn eagf ecgf (Right rss) =- let- errmsg1 = "Rule not in proof step ruleset(s): "- errmsg2 = "Error in proof step antecedent graph(s): "- errmsg3 = "Error in proof step consequent graph: "- in- do { let mrul = getMaybeContextRule rn rss :: Maybe RDFRule- ; esag <- sequence eagf -- [Either String RDFFormula]]- ; let eags = sequence esag :: Either String [RDFFormula]- ; ecg <- ecgf -- Either String RDFFormula- ; let est = case (mrul,eags,ecg) of- (Nothing,_,_) -> Left (errmsg1++show rn)- (_,Left er,_) -> Left (errmsg2++er)- (_,_,Left er) -> Left (errmsg3++er)- (Just rul,Right ags,Right cg) ->- Right $ makeRDFProofStep rul ags cg- ; return est- }--ssFwdChain ::- ScopedName -- ruleset name- -> ScopedName -- rule name- -> [SwishStateIO (Either String RDFGraph)] -- antecedent graphs- -> ScopedName -- consequent graph name- -> NamespaceMap -- prefixes for new graph- -> SwishStateIO ()-ssFwdChain sn rn agfs cn prefs =- let- errmsg1 = "FwdChain rule error: "- errmsg2 = "FwdChain antecedent error: "- in- do { erl <- ssFindRulesetRule sn rn- ; aesg <- sequence agfs -- [Either String RDFGraph]- ; let eags = sequence aesg :: Either String [RDFGraph]- ; let fcr = case (erl,eags) of- (Left er,_) -> setError (errmsg1++er)- (_,Left er) -> setError (errmsg2++er)- (Right rl,Right ags) ->- modGraphs (mapReplaceOrAdd (NamedGraph cn [cg]))- where- cg = case fwdApply rl ags of- [] -> emptyRDFGraph- grs -> setNamespaces prefs $ foldl1 add grs- ; modify fcr- }--ssFindRulesetRule ::- ScopedName -> ScopedName -> SwishStateIO (Either String RDFRule)-ssFindRulesetRule sn rn = gets find- where- find st = case findRuleset sn st of- Nothing -> Left ("Ruleset not found: "++show sn)- Just rs -> find1 rs- find1 rs = case getRulesetRule rn rs of- Nothing -> Left ("Rule not in ruleset: "++show sn++": "++show rn)- Just rl -> Right rl--ssFindRuleset ::- ScopedName -> SwishStateIO (Either String RDFRuleset)-ssFindRuleset sn = gets find- where- find st = case findRuleset sn st of- Nothing -> Left ("Ruleset not found: "++show sn)- Just rs -> Right rs--ssBwdChain ::- ScopedName -- ruleset name- -> ScopedName -- rule name- -> SwishStateIO (Either String RDFGraph) -- consequent graphs- -> ScopedName -- antecedent alts name- -> NamespaceMap -- prefixes for new graphs- -> SwishStateIO ()-ssBwdChain sn rn cgf an prefs =- let- errmsg1 = "BwdChain rule error: "- errmsg2 = "BwdChain goal error: "- in- do { erl <- ssFindRulesetRule sn rn- ; ecg <- cgf -- Either String RDFGraph- ; let fcr = case (erl,ecg) of- (Left er,_) -> setError (errmsg1++er)- (_,Left er) -> setError (errmsg2++er)- (Right rl,Right cg) ->- modGraphs (mapReplaceOrAdd (NamedGraph an ags))- where- ags = map mergegr (bwdApply rl cg)- mergegr grs = case grs of- [] -> emptyRDFGraph- _ -> setNamespaces prefs $ foldl1 add grs- ; modify fcr- }---- Temporary implementation: just read local file WNH --- (Add logic to separate filenames from URIs, and--- attempt HTTP GET, or similar.)-getResourceData :: Maybe URI -> SwishStateIO (Either String L.Text)-getResourceData muri =- case muri of- Nothing -> fromStdin- Just uri -> fromUri uri- where- fromStdin =- do { dat <- lift LIO.getContents- ; return $ Right dat- }- fromUri = fromFile- fromFile uri | uriScheme uri == "file:" = Right `fmap` lift (LIO.readFile $ uriPath uri)- | otherwise = error $ "Unsupported file name for read: " ++ show uri- --- Temporary implementation: just write local file--- (Need to add logic to separate filenames from URIs, and--- attempt HTTP PUT, or similar.)-putResourceData :: Maybe URI -> B.Builder -> SwishStateIO ()-putResourceData muri gsh =- do { ios <- lift $ IO.try $- case muri of- Nothing -> toStdout- Just uri -> toUri uri- ; case ios of- Left ioe -> modify $ setError- ("Error writing graph: "++- IO.ioeGetErrorString ioe)- Right a -> return a- }- where- toStdout = LIO.putStrLn gstr- toUri uri | uriScheme uri == "file:" = LIO.writeFile (uriPath uri) gstr- | otherwise = error $ "Unsupported file name for write: " ++ show uri- gstr = B.toLazyText gsh--{- $syntax--The script syntax is based loosely on Notation3, and the script parser is an-extension of the Notation3 parser in the module "Swish.RDF.N3Parser".-The comment character is @#@ and white space is ignored.--> script := command *-> command := prefixLine |-> nameItem |-> readGraph |-> writeGraph |-> mergeGraphs |-> compareGraphs |-> assertEquiv |-> assertMember |-> defineRule |-> defineRuleset |-> defineConstraints |-> checkProofCmd |-> fwdChain |-> bwdChain ---}--{- $prefixLine--> prefixLine := @prefix [<prefix>]: <uri> .--Define a namespace prefix and URI. --The prefix thus defined is available for use in any subsequent script-command, and also in any graphs contained within the script file. (So,-prefix declarations do not need to be repeated for each graph-contained within the script.)--Graphs read from external files must contain their own prefix-declarations.--Example:-- > @prefix gex: <http://example1.com/graphs/>.- > @prefix : <http://example2.com/id/>.---}--{- $nameItem--> nameItem := name :- graph |-> name :- ( graph* )--Graphs or lists of graphs can be given a name for use in other-statements. A name is a qname (prefix:local) or a URI enclosed in-angle--Example:--> @prefix ex1: <http://example.com/graphs/> .-> @prefix ex2: <http://example.com/statements/> .->-> ex1:gr1 :- { -> ex2:foo a ex2:Foo .-> ex2:bar a ex2:Bar .-> ex2:Bar rdfs:subClassOf ex2:Foo .-> }---}--{- $readGraph--> readGraph := @read name [<uri>]--The @\@read@ command reads in the contents of the given URI-- which at present only supports reading local files, so-no HTTP access - and stores it under the given name.--If no URI is given then the file is read from standard input.--Example:-- > @prefix ex: <http://example.com/> .- > @read ex:foo <foo.n3>---}--{- $writeGraph--> writeGraph := @write name [<uri>] ; comment--The @\@write@ command writes out the contents of the given graph-- which at present only supports writing local files, so-no HTTP access. The comment text is written as a comment line-preceeding the graph contents.--If no URI is given then the file is written to the standard output.--Example:-- > @prefix ex: <http://example.com/> .- > @read ex:gr1 <graph1.n3>- > @read ex:gr2 <graph2.n3>- > @merge (ex:gr1 ex:gr2) => ex:gr3- > @write ex:gr3 ; the merged data- > @write ex:gr3 <merged.n3> ; merge of graph1.n3 and graph2.n3---}--{- $mergeGraphs--> mergeGraphs := @merge ( name* ) => name--Create a new named graph that is the merge two or more graphs,-renaming bnodes as required to avoid node-merging.--When the merge command is run, the message-- > # Merge: <output graph name>--will be created on the standard output channel.--Example:-- > @prefix gex: <http://example.com/graph/>.- > @prefix ex: <http://example.com/statements/>.- > gex:gr1 :- { ex:foo ex:bar _:b1 . }- > gex:gr2 :- { _:b1 ex:foobar 23. }- > @merge (gex:gr1 gex:gr2) => gex:gr3- > @write gex:gr3 ; merged graphs--When run in Swish, this creates the following output (along with-several other namespace declarations):-- > # merged graphs- > @prefix ex: <http://example.com/statements/> .- > ex:foo ex:bar [] .- > [- > ex:foobar "23"^^xsd:integer- > ] .---}--{- $compareGraphs--> compareGraphs := @compare name name--Compare two graphs for isomorphism, setting the Swish exit status to-reflect the result.--When the compare command is run, the message-- > # Compare: <graph1> <graph2>--will be created on the standard output channel.--Example:-- > @prefix gex: <http://example.com/graphs/>.- > @read gex:gr1 <graph1.n3>- > @read gex:gr2 <graph2.n3>- > @compare gex:gr1 gex:gr2---}--{- $assertEquiv--> assertEquiv := @asserteq name name ; comment--Test two graphs or lists of graphs for isomorphism, reporting if they-differ. The comment text is included with any report generated.--When the command is run, the message-- > # AssertEq: <comment>--will be created on the standard output channel.--Example:-- > @prefix ex: <http://id.ninebynine.org/wip/2003/swishtest/> .- >- > # Set up the graphs for the rules- > ex:Rule01Ant :- { ?p ex:son ?o . }- > ex:Rule01Con :- { ?o a ex:Male ; ex:parent ?p . }- >- > # Create a rule and a ruleset- > @rule ex:Rule01 :- ( ex:Rule01Ant ) => ex:Rule01Con- > @ruleset ex:rules :- (ex:TomSonDick ex:TomSonHarry) ; (ex:Rule01)- >- > # Apply the rule- > @fwdchain ex:rules ex:Rule01 { :Tom ex:son :Charles . } => ex:Rule01fwd- >- > # Compare the results to the expected value- > ex:ExpectedRule01fwd :- { :Charles a ex:Male ; ex:parent :Tom . } - > @asserteq ex:Rule01fwd ex:ExpectedRule01fwd- > ; Infer that Charles is male and has parent Tom---}--{- $assertMember--> assertMember := @assertin name name ; comment--Test if a graph is isomorphic to a member of a list of graphs,-reporting if no match is found. The comment text is included with any-report generated.--Example:--> @bwdchain pv:rules :PassengerVehicle ex:Test01Inp <= :t1b-> -> @assertin ex:Test01Bwd0 :t1b ; Backward chain component test (0)-> @assertin ex:Test01Bwd1 :t1b ; Backward chain component test (1)---}--{- $defineRule--> defineRule := @rule name :- ( name* ) => name-> defineRule := @rule name :- ( name* ) => name-> | ( (name var*)* )--Define a named Horn-style rule. --The list of names preceding and following @=>@ are the antecedent and consequent-graphs, respectivelu. Both sets may contain variable nodes of the form -@?var@.--The optional part, after the @|@ separator, is a list of variable-binding modifiers, each of which consists of a name and a list of-variables (@?var@) to which the modifier is applied. Variable binding-modifiers are built in to Swish, and are used to incorporate datatype-value inferences into a rule. ---}--{- $defineRuleset--> defineRuleset := @ruleset name :- ( name* ) ; ( name* ) --Define a named ruleset (a collection of axioms and rules). The first-list of names are the axioms that are part of the ruleset, and the-second list are the rules.---}--{- $defineConstraints--> defineConstraints := @constraints pref :- ( name* ) | [ name | ( name* ) ]--Define a named ruleset containing class-restriction rules based on a-datatype value constraint. The first list of-names is a list of graphs that together comprise the class-restriction-definitions (rule names are the names of the corresponding restriction-classes). The second list of names is a list of datatypes whose-datatype relations are referenced by the class restriction-definitions.---}--{- $fwdChain--> fwdChain := @fwdchain pref name ( name* ) => name--Define a new graph obtained by forward-chaining a rule. The first name-is the ruleset to be used. The second name is the rule name. The list-of names are the antecedent graphs to which the rule is applied. The-name following the @=>@ names a new graph that is the result of formward-chaining from the given antecedents using the indicated rule.---}--{- $bwdChain--> bwdChain := @bwdchain pref name graph <= name--Define a new list of alternative graphs obtained by backward-chaining-a rule. The first name is the ruleset to be used. The second name is-the rule name. The third name (before the @<=@) is the name of a goal graph-from which to backward chain. The final name (after the @<=@) names a new-list of graphs, each of which is an alternative antecedent from which-the given goal can be deduced using the indicated rule.----}--{- $proof--> checkProofCmd := proofLine nl-> inputLine nl-> (stepLine nl)*-> resultLine-> proofLine := @proof name ( name* )--Check a proof, reporting the step that fails, if any.--The @\@proof@ line names the proof and specifies a list rulesets-(proof context) used. The remaining lines specify the input-expression (@\@input@), proof steps (@\@step@) and the final result-(@\@result@) that is demonstrated by the proof.--> inputLine := @input name--In a proof, indicates an input expression upon which the proof is-based. Exactly one of these immediately follows the @\@proof@ command.--> stepLine := @step name ( name* ) => name--This defines a step of the proof; any number of these immediately-follow the @\@input@ command.--It indicates the name of the rule applied for this step, a list of-antecedent graphs, and a named graph that is deduced by this step.-For convenience, the deduced graph may introduce a new named graph-using an expression of the form:-- > name :- { statements }--> resultLine := @result name--This defines the goal of the proof, and completes a proof-definition. Exactly one of these immediately follows the @\@step@-commands. For convenience, the result statement may introduce a new-named graph using an expression of the form:-- > name :- { statements }---}--{- $exampleScript--This is the example script taken from-<http://www.ninebynine.org/Software/swish-0.2.1.html#sec-script-example>-with the proof step adjusted so that it passes.--> # -- Example Swish script ---> #-> # Comment lines start with a '#'-> #-> # The script syntax is loosely based on Notation3, but it is a quite -> # different language, except that embedded graphs (enclosed in {...})-> # are encoded using Notation3 syntax.-> #-> # -- Prefix declarations ---> #-> # As well as being used for all labels defined and used by the script-> # itself, these are applied to all graph expressions within the script -> # file, and to graphs created by scripted inferences, -> # but are not applied to any graphs read in from an external source.-> -> @prefix ex: <http://id.ninebynine.org/wip/2003/swishtest/> .-> @prefix pv: <http://id.ninebynine.org/wip/2003/swishtest/pv/> .-> @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .-> @prefix xsd_integer: <http://id.ninebynine.org/2003/XMLSchema/integer#> .-> @prefix rs_rdf: <http://id.ninebynine.org/2003/Ruleset/rdf#> .-> @prefix rs_rdfs: <http://id.ninebynine.org/2003/Ruleset/rdfs#> .-> @prefix : <http://id.ninebynine.org/default/> .-> -> # Additionally, prefix declarations are provided automatically for:-> # @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .-> # @prefix rdfs: <file://www.w3.org/2000/01/rdf-schema#> .-> # @prefix rdfd: <http://id.ninebynine.org/2003/rdfext/rdfd#> .-> # @prefix rdfo: <http://id.ninebynine.org/2003/rdfext/rdfo#> .-> # @prefix owl: <http://www.w3.org/2002/07/owl#> .-> -> # -- Simple named graph declarations ---> -> ex:Rule01Ant :- { ?p ex:son ?o . }-> -> ex:Rule01Con :- { ?o a ex:Male ; ex:parent ?p . }-> -> ex:TomSonDick :- { :Tom ex:son :Dick . }-> ex:TomSonHarry :- { :Tom ex:son :Harry . }-> -> # -- Named rule definition ---> -> @rule ex:Rule01 :- ( ex:Rule01Ant ) => ex:Rule01Con-> -> # -- Named ruleset definition ---> #-> # A 'ruleset' is a collection of axioms and rules.-> #-> # Currently, the ruleset is identified using the namespace alone;-> # i.e. the 'rules' in 'ex:rules' below is not used. -> # This is under review.-> -> @ruleset ex:rules :- (ex:TomSonDick ex:TomSonHarry) ; (ex:Rule01)-> -> # -- Forward application of rule ---> #-> # The rule is identified here by ruleset and a name within the ruleset.-> -> @fwdchain ex:rules ex:Rule01 { :Tom ex:son :Charles . } => ex:Rule01fwd-> -> # -- Compare graphs ---> #-> # Compare result of inference with expected result.-> # This is a graph isomorphism test rather than strict equality, -> # to allow for bnode renaming.-> # If the graphs are not equal, a message is generated, which-> # includes the comment (';' to end of line)-> -> ex:ExpectedRule01fwd :- { :Charles a ex:Male ; ex:parent :Tom . } -> -> @asserteq ex:Rule01fwd ex:ExpectedRule01fwd-> ; Infer that Charles is male and has parent Tom-> -> # -- Display graph (to screen and a file) ---> #-> # The comment is included in the output.-> -> @write ex:Rule01fwd ; Charles is male and has parent Tom-> @write ex:Rule01fwd <Example1.n3> ; Charles is male and has parent Tom-> -> # -- Read graph from file ---> #-> # Creates a new named graph in the Swish environment.-> -> @read ex:Rule01inp <Example1.n3>-> -> # -- Proof check ---> #-> # This proof uses the built-in RDF and RDFS rulesets, -> # which are the RDF- and RDFS- entailment rules described in the RDF-> # formal semantics document.-> #-> # To prove:-> # ex:foo ex:prop "a" .-> # RDFS-entails-> # ex:foo ex:prop _:x .-> # _:x rdf:type rdfs:Resource .-> #-> # If the proof is not valid according to the axioms and rules of the -> # ruleset(s) used and antecedents given, then an error is reported -> # indicating the failed proof step.-> -> ex:Input :- { ex:foo ex:prop "a" . }-> ex:Result :- { ex:foo ex:prop _:a . _:a rdf:type rdfs:Resource . }-> -> @proof ex:Proof ( rs_rdf:rules rs_rdfs:rules )-> @input ex:Input-> @step rs_rdfs:r3 ( rs_rdfs:a10 rs_rdfs:a39 )-> => ex:Stepa :- { rdfs:Literal rdf:type rdfs:Class . }-> @step rs_rdfs:r8 ( ex:Stepa )-> => ex:Stepb :- { rdfs:Literal rdfs:subClassOf rdfs:Resource . }-> @step rs_rdf:lg ( ex:Input )-> => ex:Stepc :- { ex:foo ex:prop _:a . _:a rdf:_allocatedTo "a" . }-> @step rs_rdfs:r1 ( ex:Stepc )-> => ex:Stepd :- { _:a rdf:type rdfs:Literal . }-> @step rs_rdfs:r9 ( ex:Stepb ex:Stepd )-> => ex:Stepe :- { _:a rdf:type rdfs:Resource . }-> @step rs_rdf:se ( ex:Stepc ex:Stepd ex:Stepe )-> => ex:Result-> @result ex:Result-> -> # -- Restriction based datatype inferencing ---> #-> # Datatype inferencing based on a general class restriction and-> # a predefined relation (per idea noted by Pan and Horrocks).-> -> ex:VehicleRule :--> { :PassengerVehicle a rdfd:GeneralRestriction ;-> rdfd:onProperties (:totalCapacity :seatedCapacity :standingCapacity) ;-> rdfd:constraint xsd_integer:sum ;-> rdfd:maxCardinality "1"^^xsd:nonNegativeInteger . }-> -> # Define a new ruleset based on a declaration of a constraint class-> # and reference to built-in datatype.-> # The datatype constraint xsd_integer:sum is part of the definition -> # of datatype xsd:integer that is cited in the constraint ruleset-> # declaration. It relates named properties of a class instance.-> -> @constraints pv:rules :- ( ex:VehicleRule ) | xsd:integer-> -> # Input data for test cases:-> -> ex:Test01Inp :--> { _:a1 a :PassengerVehicle ;-> :seatedCapacity "30"^^xsd:integer ;-> :standingCapacity "20"^^xsd:integer . }-> -> # Forward chaining test case:-> -> ex:Test01Fwd :- { _:a1 :totalCapacity "50"^^xsd:integer . }-> -> @fwdchain pv:rules :PassengerVehicle ex:Test01Inp => :t1f-> @asserteq :t1f ex:Test01Fwd ; Forward chain test-> -> # Backward chaining test case:-> #-> # Note that the result of backward chaining is a list of alternatives,-> # any one of which is sufficient to derive the given conclusion.-> -> ex:Test01Bwd0 :--> { _:a1 a :PassengerVehicle .-> _:a1 :totalCapacity "50"^^xsd:integer .-> _:a1 :seatedCapacity "30"^^xsd:integer . }-> -> ex:Test01Bwd1 :--> { _:a1 a :PassengerVehicle .-> _:a1 :totalCapacity "50"^^xsd:integer .-> _:a1 :standingCapacity "20"^^xsd:integer . }-> -> # Declare list of graphs:-> -> ex:Test01Bwd :- ( ex:Test01Bwd0 ex:Test01Bwd1 )-> -> @bwdchain pv:rules :PassengerVehicle ex:Test01Inp <= :t1b-> @asserteq :t1b ex:Test01Bwd ; Backward chain test-> -> # Can test for graph membership in a list-> -> @assertin ex:Test01Bwd0 :t1b ; Backward chain component test (0)-> @assertin ex:Test01Bwd1 :t1b ; Backward chain component test (1)-> -> # -- Merge graphs ---> #-> # Merging renames bnodes to avoid collisions.-> -> @merge ( ex:Test01Bwd0 ex:Test01Bwd1 ) => ex:Merged-> -> # This form of comparison sets the Swish exit status based on the result.-> -> ex:ExpectedMerged :--> { _:a1 a :PassengerVehicle .-> _:a1 :totalCapacity "50"^^xsd:integer .-> _:a1 :seatedCapacity "30"^^xsd:integer .-> _:a2 a :PassengerVehicle .-> _:a2 :totalCapacity "50"^^xsd:integer .-> _:a2 :standingCapacity "20"^^xsd:integer . }-> -> @compare ex:Merged ex:ExpectedMerged-> -> # End of example script--If saved in the file example.ss, then it can be evaluated by saying-either of:--> % Swish -s=example.ss--or, from @ghci@:--> Prelude> :m + Swish.RDF.SwishMain-> Prelude Swish.RDF.SwishMain> :set prompt "Swish> "-> Swish> runSwish "-s=example.ss"--and the output is--> # AssertEq: Infer that Charles is male and has parent Tom-> # Charles is male and has parent Tom-> @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .-> @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .-> @prefix rdfd: <http://id.ninebynine.org/2003/rdfext/rdfd#> .-> @prefix owl: <http://www.w3.org/2002/07/owl#> .-> @prefix log: <http://www.w3.org/2000/10/swap/log#> .-> @prefix : <http://id.ninebynine.org/default/> .-> @prefix ex: <http://id.ninebynine.org/wip/2003/swishtest/> .-> @prefix pv: <http://id.ninebynine.org/wip/2003/swishtest/pv/> .-> @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .-> @prefix xsd_integer: <http://id.ninebynine.org/2003/XMLSchema/integer#> .-> @prefix rs_rdf: <http://id.ninebynine.org/2003/Ruleset/rdf#> .-> @prefix rs_rdfs: <http://id.ninebynine.org/2003/Ruleset/rdfs#> .-> :Charles ex:parent :Tom ;-> a ex:Male .-> -> Proof satisfied: ex:Proof-> # AssertEq: Forward chain test-> # AssertEq: Backward chain test-> # AssertIn: Backward chain component test (0)-> # AssertIn: Backward chain component test (1)-> # Merge: ex:Merged-> # Compare: ex:Merged ex:ExpectedMerged---}---------------------------------------------------------------------------------------- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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/RDF/TurtleFormatter.hs
@@ -1,885 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}------------------------------------------------------------------------------------- See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : TurtleFormatter--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke--- License : GPL V2------ Maintainer : Douglas Burke--- Stability : experimental--- Portability : OverloadedStrings------ This Module implements a Turtle formatter (see [1])--- for an RDFGraph value.------ REFERENCES:------ 1 <http://www.w3.org/TR/turtle/>--- Turtle, Terse RDF Triple Language--- W3C Working Draft 09 August 2011 (<http://www.w3.org/TR/2011/WD-turtle-20110809/>)--------------------------------------------------------------------------------------{--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.TurtleFormatter- ( NodeGenLookupMap- , formatGraphAsText- , formatGraphAsLazyText- , formatGraphAsBuilder- , formatGraphIndent - , formatGraphDiag- )-where--import Swish.RDF.RDFGraph (- RDFGraph, RDFLabel(..)- , NamespaceMap, RevNamespaceMap- -- emptyNamespaceMap,- -- FormulaMap, emptyFormulaMap,- , getArcs- , labels- -- , setNamespaces- , getNamespaces,- -- getFormulae,- emptyRDFGraph- , quote- , quoteT- , resRdfFirst, resRdfRest, resRdfNil- )--import Swish.RDF.Vocabulary (- isLang- , langTag - , rdfType- , rdfNil- , xsdBoolean, xsdDecimal, xsdInteger, xsdDouble - )--import Swish.RDF.GraphClass (Arc(..))--import Swish.Utils.LookupMap- ( LookupEntryClass(..)- , LookupMap, emptyLookupMap, reverseLookupMap- , listLookupMap- , mapFind, mapFindMaybe, mapAdd- -- , mapDelete- , mapMerge- )--import Swish.Utils.Namespace (ScopedName, getScopeLocal, getScopeURI)--import Data.Char (isDigit)--import Data.List (foldl', delete, groupBy, partition, sort, intersperse)--import Data.Monoid (Monoid(..))-import Control.Monad (liftM, when)-import Control.Monad.State (State, modify, get, put, runState)---- 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.Text as T-import qualified Data.Text.Lazy as L-import qualified Data.Text.Lazy.Builder as B---- temporary conversion-quoteB :: Bool -> String -> B.Builder-quoteB f v = B.fromString $ quote f v--------------------------------------------------------------------------- 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).--type SubjTree lb = [(lb,PredTree lb)]-type PredTree lb = [(lb,[lb])]--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)]- , nodeGenSt :: NodeGenState- , bNodesCheck :: [RDFLabel] -- these bNodes are not to be converted to '[..]' format- , traceBuf :: [String]- }- -type Formatter a = State TurtleFormatterState a--emptyTFS :: NodeGenState -> TurtleFormatterState-emptyTFS ngs = TFS- { indent = "\n"- , lineBreak = False- , graph = emptyRDFGraph- , subjs = []- , props = []- , objs = []- -- , formAvail = emptyFormulaMap- -- , formQueue = []- , nodeGenSt = ngs- , bNodesCheck = []- , traceBuf = []- }---- | Node name generation state information that carries through--- and is updated by nested formulae-type NodeGenLookupMap = LookupMap (RDFLabel,Int)--data NodeGenState = Ngs- { prefixes :: NamespaceMap- , nodeMap :: NodeGenLookupMap- , nodeGen :: Int- }--emptyNgs :: NodeGenState-emptyNgs = Ngs- { prefixes = emptyLookupMap- , nodeMap = emptyLookupMap- , nodeGen = 0- }---- simple context for label creation--- (may be a temporary solution to the problem--- of label creation)----data LabelContext = SubjContext | PredContext | ObjContext- deriving (Eq, Show)--getIndent :: Formatter B.Builder-getIndent = indent `liftM` get--setIndent :: B.Builder -> Formatter ()-setIndent ind = modify $ \st -> st { indent = ind }--getLineBreak :: Formatter Bool-getLineBreak = lineBreak `liftM` get--setLineBreak :: Bool -> Formatter ()-setLineBreak brk = modify $ \st -> st { lineBreak = brk }--getNgs :: Formatter NodeGenState-getNgs = nodeGenSt `liftM` get--setNgs :: NodeGenState -> Formatter ()-setNgs ngs = modify $ \st -> st { nodeGenSt = ngs }--getPrefixes :: Formatter NamespaceMap-getPrefixes = prefixes `liftM` getNgs--getSubjs :: Formatter (SubjTree RDFLabel)-getSubjs = subjs `liftM` get--setSubjs :: SubjTree RDFLabel -> Formatter ()-setSubjs sl = modify $ \st -> st { subjs = sl }--getProps :: Formatter (PredTree RDFLabel)-getProps = props `liftM` get--setProps :: PredTree RDFLabel -> Formatter ()-setProps ps = modify $ \st -> st { props = ps }--{--getObjs :: Formatter ([RDFLabel])-getObjs = objs `liftM` get--setObjs :: [RDFLabel] -> Formatter ()-setObjs os = do- st <- get- put $ st { objs = os }--}--getBnodesCheck :: Formatter [RDFLabel]-getBnodesCheck = bNodesCheck `liftM` get--{--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 = mapDelete fa fn,- formQueue = (fn,fv) : formQueue st- }- case mapFindMaybe fn fa of- Nothing -> return ()- Just v -> put (newState v) >> return ()--}--{--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 fa = formAvail st- newState = st { formAvail=mapDelete fa fn }- case mapFindMaybe fn fa of- Nothing -> return Nothing- Just fv -> put newState >> return (Just fv)---}--{--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---}---- list has a length of 1-len1 :: [a] -> Bool-len1 (_:[]) = True-len1 _ = False--{-|-Given a set of statements and a label, return the details of the-RDF collection referred to by label, or Nothing.--For label to be considered as representing a collection we require the-following conditions to hold (this is only to support the-serialisation using the '(..)' syntax and does not make any statement-about semantics of the statements with regard to RDF Collections):-- - there must be one rdf_first and one rdfRest statement- - there must be no other predicates for the label---} -getCollection :: - SubjTree RDFLabel -- ^ statements organized by subject- -> RDFLabel -- ^ does this label represent a list?- -> Maybe (SubjTree RDFLabel, [RDFLabel], [RDFLabel])- -- ^ the statements with the elements removed; the- -- content elements of the collection (the objects of the rdf:first- -- predicate) and the nodes that represent the spine of the- -- collection (in reverse order, unlike the actual contents which are in- -- order).-getCollection subjList lbl = go subjList lbl ([],[]) - where- go sl l (cs,ss) | l == resRdfNil = Just (sl, reverse cs, ss)- | otherwise = do- (pList1, sl') <- removeItem sl l- (pFirst, pList2) <- removeItem pList1 resRdfFirst- (pNext, pList3) <- removeItem pList2 resRdfRest-- -- QUS: could I include these checks implicitly in the pattern matches above?- -- ie instrad of (pFirst, pos1) <- ..- -- have ([content], pos1) <- ...- -- ?- if and [len1 pFirst, len1 pNext, null pList3]- then go sl' (head pNext) (head pFirst : cs, l : ss)- else Nothing--{--TODO:--Should we change the preds/objs entries as well?---}-extractList :: LabelContext -> RDFLabel -> Formatter (Maybe [RDFLabel])-extractList lctxt ln = do- osubjs <- getSubjs- oprops <- getProps- let mlst = getCollection osubjs' ln-- -- we only want to send in rdf:first/rdf:rest here- fprops = filter ((`elem` [resRdfFirst, resRdfRest]) . fst) oprops-- osubjs' =- case lctxt of- SubjContext -> (ln, fprops) : osubjs- _ -> osubjs -- -- tr = "extractList " ++ show ln ++ " (" ++ show lctxt ++ ")\n -> osubjs= " ++ show osubjs ++ "\n -> opreds= " ++ show oprops ++ "\n -> mlst= " ++ show mlst ++ "\n"- -- addTrace tr- case mlst of- -- sl is guaranteed to be free of (ln,fprops) here if lctxt is SubjContext- Just (sl,ls,_) -> do- setSubjs sl- when (lctxt == SubjContext) $ setProps $ filter ((`notElem` [resRdfFirst, resRdfRest]) . fst) oprops- return (Just ls)-- Nothing -> return Nothing- -{-|-Removes the first occurrence of the item from the-association list, returning it's contents and the rest-of the list, if it exists.--}-removeItem :: (Eq a) => [(a,b)] -> a -> Maybe (b, [(a,b)])-removeItem os x =- let (as, bs) = break (\a -> fst a == x) os- in case bs of- ((_,b):bbs) -> Just (b, as ++ bbs)- [] -> Nothing--------------------------------------------------------------------------- Define a top-level formatter function:-------------------------------------------------------------------------formatGraphAsText :: RDFGraph -> T.Text-formatGraphAsText = L.toStrict . formatGraphAsLazyText--formatGraphAsLazyText :: RDFGraph -> L.Text-formatGraphAsLazyText = B.toLazyText . formatGraphAsBuilder- -formatGraphAsBuilder :: RDFGraph -> B.Builder-formatGraphAsBuilder = formatGraphIndent "\n" True- -formatGraphIndent :: B.Builder -> Bool -> RDFGraph -> 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, Int, [String])-formatGraphDiag indnt flag gr = - let fg = formatGraph indnt " .\n" False flag gr- ngs = emptyNgs {- prefixes = emptyLookupMap,- 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 ind end dobreak dopref gr = do- setIndent ind- setLineBreak dobreak- setGraph gr- - fp <- if dopref- then formatPrefixes (getNamespaces gr)- else return mempty- more <- moreSubjects- if more- then do- fr <- formatSubjects- return $ mconcat [fp, fr, end]- else return fp--formatPrefixes :: NamespaceMap -> Formatter B.Builder-formatPrefixes pmap = do- let mls = map (pref . keyVal) (listLookupMap pmap)- ls <- sequence mls- return $ mconcat ls- where- pref (Just p,u) = nextLine $ mconcat ["@prefix ", B.fromText p, ": <", quoteB True (show u), "> ."]- pref (_,u) = nextLine $ mconcat ["@prefix : <", quoteB True (show u), "> ."]--{--NOTE:-I expect there to be confusion below where I need to-convert from Text to Builder--}--formatSubjects :: Formatter B.Builder-formatSubjects = do- sb <- nextSubject- sbstr <- formatLabel SubjContext sb- - flagP <- moreProperties- if flagP- then do- prstr <- formatProperties sb sbstr- flagS <- moreSubjects- if flagS- then do- fr <- formatSubjects- return $ mconcat [prstr, " .", fr]- else return prstr- - else do- txt <- nextLine sbstr- - flagS <- moreSubjects- if flagS- then do- fr <- formatSubjects- return $ mconcat [txt, " .", fr]- else return txt--{--TODO: now we are throwing a Builder around it is awkward to-get the length of the text to calculate the indentation--So-- a) change the indentation scheme- b) pass around text instead of builder--mkIndent :: L.Text -> L.Text-mkIndent inVal = L.replicate (L.length inVal) " "--}--hackIndent :: B.Builder-hackIndent = " "--formatProperties :: RDFLabel -> B.Builder -> Formatter B.Builder-formatProperties sb sbstr = do- pr <- nextProperty sb- prstr <- formatLabel PredContext pr- obstr <- formatObjects sb pr $ mconcat [sbstr, " ", prstr]- more <- moreProperties- let sbindent = hackIndent -- mkIndent sbstr- if more- then do- fr <- formatProperties sb sbindent- nl <- nextLine $ obstr `mappend` " ;"- return $ nl `mappend` fr- else nextLine obstr--formatObjects :: RDFLabel -> RDFLabel -> B.Builder -> Formatter B.Builder-formatObjects sb pr prstr = do- ob <- nextObject sb pr- obstr <- formatLabel ObjContext ob- more <- moreObjects- if more- then do- let prindent = hackIndent -- mkIndent prstr- fr <- formatObjects sb pr prindent- nl <- nextLine $ mconcat [prstr, " ", obstr, ","]- return $ nl `mappend` fr- else return $ mconcat [prstr, " ", obstr]--{--Add a list inline. We are given the labels that constitute-the list, in order, so just need to display them surrounded-by ().--}-insertList :: [RDFLabel] -> Formatter B.Builder-insertList [] = return "()" -- not convinced this can happen-insertList xs = do- ls <- mapM (formatLabel ObjContext) xs- return $ mconcat ("( " : intersperse " " ls) `mappend` " )"- -{--Add a blank node inline.--}--insertBnode :: LabelContext -> RDFLabel -> Formatter B.Builder-insertBnode SubjContext lbl = do- -- a safety check- flag <- moreProperties- if flag- then do- txt <- (`mappend` "\n") `liftM` formatProperties lbl ""- return $ mconcat ["[] ", txt]- else error $ "Internal error: expected properties with label: " ++ show lbl--insertBnode _ lbl = do- ost <- get- let osubjs = subjs ost- oprops = props ost- oobjs = objs ost-- (bsubj, rsubjs) = partition ((== lbl) . fst) osubjs-- rprops = case bsubj of- [(_,rs)] -> rs- _ -> []-- -- we essentially want to create a new subgraph- -- for this node but it's not as simple as that since- -- we could have something like- -- :a :b [ :foo [ :bar "xx" ] ]- -- so we still need to carry around the whole graph- --- nst = ost { subjs = rsubjs,- props = rprops,- objs = []- }-- put nst- flag <- moreProperties- txt <- if flag- then (`mappend` "\n") `liftM` formatProperties lbl ""- else return ""-- -- TODO: how do we restore the original set up?- -- I can't believe the following is sufficient- --- nst' <- get- let slist = map fst $ subjs nst'- nsubjs = filter (\(l,_) -> l `elem` slist) osubjs-- put $ nst' { subjs = nsubjs,- props = oprops, - objs = oobjs- }-- -- TODO: handle indentation?- return $ mconcat ["[", txt, "]"]- -------------------------------------------------------------------------- Formatting helpers-------------------------------------------------------------------------setGraph :: RDFGraph -> Formatter ()-setGraph gr = do- st <- get-- let ngs0 = nodeGenSt st- pre' = mapMerge (prefixes ngs0) (getNamespaces gr)- ngs' = ngs0 { prefixes = pre' }- arcs = sortArcs $ getArcs gr- nst = st { graph = gr- , subjs = arcTree arcs- , props = []- , objs = []- -- , formAvail = getFormulae gr- , nodeGenSt = ngs'- , bNodesCheck = countBnodes arcs- }-- put nst--hasMore :: (TurtleFormatterState -> [b]) -> Formatter Bool-hasMore lens = (not . null . lens) `liftM` get--moreSubjects :: Formatter Bool-moreSubjects = hasMore subjs--- moreSubjects = (not . null . subjs) `liftM` get--moreProperties :: Formatter Bool-moreProperties = hasMore props--- moreProperties = (not . null . props) `liftM` get--moreObjects :: Formatter Bool-moreObjects = hasMore objs--- moreObjects = (not . null . objs) `liftM` get--nextSubject :: Formatter RDFLabel-nextSubject = do- st <- get-- let sb:sbs = subjs st- nst = st { subjs = sbs- , props = snd sb- , objs = []- }-- put nst- return $ fst sb--nextProperty :: RDFLabel -> Formatter RDFLabel-nextProperty _ = do- st <- get-- let pr:prs = props st- nst = st { props = prs- , objs = snd pr- }-- put nst- return $ fst pr--nextObject :: RDFLabel -> RDFLabel -> Formatter RDFLabel-nextObject _ _ = do- st <- get-- let ob:obs = objs st- nst = st { objs = obs }-- put nst- return ob--nextLine :: B.Builder -> Formatter B.Builder-nextLine str = do- ind <- getIndent- brk <- getLineBreak- if brk- then return $ ind `mappend` str- else do- -- After first line, always insert line break- setLineBreak True- return str---- 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 <uri>--- (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.--------- 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.--}-formatLabel lctxt lab@(Blank (_:_)) = do- mlst <- extractList lctxt lab- case mlst of- Just lst -> insertList 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 <- getBnodesCheck- 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 = do- pr <- getPrefixes- let nsuri = getScopeURI sn- local = getScopeLocal sn- premap = reverseLookupMap pr :: RevNamespaceMap- prefix = mapFindMaybe nsuri premap- - name = case prefix of- Just (Just p) -> B.fromText $ quoteT True $ mconcat [p, ":", local] -- TODO: what are quoting rules for QNames- _ -> mconcat ["<", quoteB True (show nsuri ++ T.unpack local), ">"]- - return name---- The canonical notation for xsd:double in XSD, with an upper-case E,--- does not match the syntax used in N3, so we need to convert here. --- Rather than converting back to a Double and then displaying that --- we just convert E to e for now. --- -formatLabel _ (Lit lit (Just dtype)) - | dtype == xsdDouble = return $ B.fromText $ T.toLower lit- | dtype `elem` [xsdBoolean, xsdDecimal, xsdInteger] = return $ B.fromText lit- | otherwise = return $ quoteText lit `mappend` formatAnnotation dtype-formatLabel _ (Lit lit Nothing) = return $ quoteText lit--formatLabel _ lab = return $ B.fromString $ show lab---- the annotation for a literal (ie type or language)-formatAnnotation :: ScopedName -> B.Builder-formatAnnotation a | isLang a = "@" `mappend` B.fromText (langTag a)- | otherwise = "^^" `mappend` showScopedName a--{--We have to decide whether to use " or """ to quote-the string.--There is also no need to restrict the string to the-ASCII character set; this could be an option but we-can also leave Unicode as is (or at least convert to UTF-8).--If we use """ to surround the string then we protect the-last character if it is a " (assuming it isn't protected).--}--quoteText :: T.Text -> B.Builder-quoteText txt = - let st = T.unpack txt -- TODO: fix- qst = quoteB (n==1) st- n = if '\n' `elem` st || '"' `elem` st then 3 else 1- qch = B.fromString (replicate n '"')- in mconcat [qch, qst, qch]--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 lab = do- ngs <- getNgs- let cmap = nodeMap ngs- cval = nodeGen ngs- nv <- case mapFind 0 lab cmap of- 0 -> do - let nval = succ cval- nmap = mapAdd cmap (lab, nval)- setNgs $ ngs { nodeGen = nval, nodeMap = nmap }- return nval- - n -> return n- - -- TODO: is this what we want?- return $ "_:swish" `mappend` B.fromString (show nv)---- TODO: need to be a bit more clever with this than we did in NTriples--- not sure the following counts as clever enough ...--- -showScopedName :: ScopedName -> B.Builder-{--showScopedName (ScopedName n l) = - let uri = nsURI n ++ l- in quote uri--}-showScopedName = quoteB True . show--------------------------------------------------------------------------- Graph-related helper functions-------------------------------------------------------------------------newtype SortedArcs lb = SA [Arc lb]--sortArcs :: (Ord lb) => [Arc lb] -> SortedArcs lb-sortArcs = SA . sort---- Rearrange a list of arcs into a tree of pairs which group together--- all statements for a single subject, and similarly for multiple--- objects of a common predicate.----arcTree :: (Eq lb) => SortedArcs lb -> SubjTree lb-arcTree (SA as) = commonFstEq (commonFstEq id) $ map spopair as- where- spopair (Arc s p o) = (s,(p,o))--{--arcTree as = map spopair $ sort as- where- spopair (Arc s p o) = (s,[(p,[o])])--}---- Rearrange a list of pairs so that multiple occurrences of the first--- are commoned up, and the supplied function is applied to each sublist--- with common first elements to obtain the corresponding second value-commonFstEq :: (Eq a) => ( [b] -> c ) -> [(a,b)] -> [(a,c)]-commonFstEq f ps =- [ (fst $ head sps,f $ map snd sps) | sps <- groupBy fstEq ps ]- where- fstEq (f1,_) (f2,_) = f1 == f2--findMaxBnode :: RDFGraph -> Int-findMaxBnode = maximum . map getAutoBnodeIndex . labels--getAutoBnodeIndex :: RDFLabel -> Int-getAutoBnodeIndex (Blank ('_':lns)) = res where- -- cf. prelude definition of read s ...- res = case [x | (x,t) <- reads lns, ("","") <- lex t] of- [x] -> x- _ -> 0-getAutoBnodeIndex _ = 0--{--Find all blank nodes that occur- - any number of times as a subject- - 0 or 1 times as an object--Such nodes can be output using the "[..]" syntax. To make it simpler-to check we actually store those nodes that can not be expanded.--Note that we do not try and expand any bNode that is used in-a predicate position.--Should probably be using the SubjTree RDFLabel structure but this-is easier for now.---}--countBnodes :: SortedArcs RDFLabel -> [RDFLabel]-countBnodes (SA as) = snd (foldl' ctr ([],[]) as)- where- -- first element of tuple are those blank nodes only seen once,- -- second element those blank nodes seen multiple times- --- inc b@(b1s,bms) l@(Blank _) | l `elem` bms = b- | l `elem` b1s = (delete l b1s, l:bms)- | otherwise = (l:b1s, bms)- inc b _ = b-- -- if the bNode appears as a predicate we instantly add it to the- -- list of nodes not to expand, even if only used once- incP b@(b1s,bms) l@(Blank _) | l `elem` bms = b- | l `elem` b1s = (delete l b1s, l:bms)- | otherwise = (b1s, l:bms)- incP b _ = b-- ctr orig (Arc _ p o) = inc (incP orig p) o---------------------------------------------------------------------------------------- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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/RDF/TurtleParser.hs
@@ -1,1062 +0,0 @@-{-# LANGUAGE OverloadedStrings #-} -- only used in 'fromMaybe "" mbase' line of parseN3------------------------------------------------------------------------------------- See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : TurtleParser--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke--- License : GPL V2------ Maintainer : Douglas Burke--- Stability : experimental--- Portability : OverloadedStrings------ This Module implements a Turtle parser (see [1]), returning a--- new 'RDFGraph' consisting of triples and namespace information parsed from--- the supplied input string, or an error indication.------ REFERENCES:------ 1 <http://www.w3.org/TR/turtle/>--- Turtle, Terse RDF Triple Language--- W3C Working Draft 09 August 2011 (<http://www.w3.org/TR/2011/WD-turtle-20110809/>)------ Notes:------ At present there is a lot of overlap with the N3 Parser.--------------------------------------------------------------------------------------module Swish.RDF.TurtleParser- ( ParseResult- , parseTurtle - , parseTurtlefromText - {-- , parseAnyfromText- , parseTextFromText, parseAltFromText- , parseNameFromText -- , parsePrefixFromText- , parseAbsURIrefFromText, parseLexURIrefFromText, parseURIref2FromText- -}- - {-- -- * Exports for parsers that embed Turtle in a bigger syntax- , TurtleParser, TurtleState(..), 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.RDF.RDFGraph- ( RDFGraph, RDFLabel(..)- , ToRDFLabel(..)- , NamespaceMap- , addArc - , setNamespaces- , emptyRDFGraph- )--import Swish.RDF.GraphClass (arc)--import Swish.Utils.LookupMap- ( LookupMap(..)- , LookupEntryClass(..)- , mapFindMaybe, mapReplaceOrAdd, mapAdd, mapReplace )--import Swish.Utils.Namespace- ( Namespace, makeNamespace- , ScopedName- , getScopeNamespace- , getScopedNameURI- , getScopeNamespace- , makeURIScopedName- , makeNSScopedName- )--import Swish.RDF.Vocabulary- ( langName- , rdfType- , rdfFirst, rdfRest, rdfNil- , xsdBoolean, xsdInteger, xsdDecimal, xsdDouble- , defaultBase- )--import Swish.RDF.RDFParser- ( ParseResult- , runParserWithError- , ignore- , noneOf- , char- , ichar- , string- , stringT- , symbol- , isymbol- , lexeme- , whiteSpace- , mkTypedLit- , hex4 - , hex8 - , appendURIs- )--import Control.Applicative-import Control.Monad (foldM)--import Network.URI (URI(..), parseURIReference)--import Data.Char (ord) -import Data.Maybe (fromMaybe, fromJust)--import qualified Data.Text as T-import qualified Data.Text.Lazy as L-import Text.ParserCombinators.Poly.StateText--------------------------------------------------------------------------- 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 :: Int -- blank node id generator- } deriving 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' = mapReplaceOrAdd (makeNamespace 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 = mapFindMaybe 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 "#"--emptyState :: - Maybe URI -- ^ starting base for the graph- -> TurtleState-emptyState mbase = - let pmap = LookupMap [makeNamespace 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-br :: String -> String -> TurtleParser a -> TurtleParser a-br lsym rsym = bracket (symbol lsym) (symbol rsym)---- this is a lot simpler than N3-atWord :: T.Text -> TurtleParser ()-atWord s = char '@' *> lexeme (stringT s) *> pure ()--{--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@(Lit _ (Just dtype)) | dtype `elem` [xsdBoolean, xsdInteger, xsdDecimal, xsdDouble] = do - ost <- stGet- let stmt = arc s p o- oldp = prefixUris ost- ogs = graphState ost- newp = mapReplaceOrAdd (getScopeNamespace dtype) 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 c = c >= 'a' && c <= 'z'-isAZ c = c >= 'A' && c <= 'Z'-isaZ c = isaz c || isAZ c-is09 c = c >= '0' && c <= '9'-isaZ09 c = isaZ c || is09 c--{--Convert a string representing a double into canonical-XSD form. The input string is assumed to be syntactically-valid so we use read rather than reads. We use the String read-rather than Text one because of issues I have had in some tests-with the accuracy of the Text one.--}-d2s :: L.Text -> RDFLabel-d2s = - let conv :: String -> Double- conv = read- in toRDFLabel . conv . L.unpack--{--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- mapReplaceOrAdd for the input namespace.- --}-operatorLabel :: ScopedName -> TurtleParser RDFLabel-operatorLabel snam = do- st <- stGet- let sns = getScopeNamespace snam- opmap = prefixUris st- pkey = entryKey sns- pval = entryVal sns- - rval = Res snam- - -- the lookup and the replacement could be fused- case mapFindMaybe pkey opmap of- Just val | val == pval -> return rval- | otherwise -> do- stUpdate $ \s -> s { prefixUris = mapReplace opmap sns }- return rval- - _ -> do- stUpdate $ \s -> s { prefixUris = mapAdd opmap sns }- return rval- -findPrefix :: T.Text -> TurtleParser Namespace-findPrefix pre = do- st <- stGet- case mapFindMaybe (Just pre) (prefixUris st) of- Just uri -> return $ makeNamespace (Just pre) uri- Nothing -> failBad $ "Undefined prefix '" ++ T.unpack pre ++ ":'."--{---Syntax productions; the Turtle NBF grammar elements are from-http://www.w3.org/TR/turtle/turtle.bnf--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) *> fullStop--{--[3] directive ::= prefixID - | base--}-directive :: TurtleParser ()-directive = lexeme (prefixID <|> base)--{--[4] prefixID ::= PREFIX PNAME_NS IRI_REF --}-prefixID :: TurtleParser ()-prefixID = do- _prefix - p <- lexeme _pnameNS- u <- _iriRef- stUpdate (setPrefix (fmap L.toStrict p) u)--{--[5] base ::= BASE IRI_REF --}-base :: TurtleParser ()-base = _base >> _iriRef >>= stUpdate . setBase--{--[6] triples ::= subject predicateObjectList --}--triples :: TurtleParser ()-triples = subject >>= predicateObjectList--{--[7] predicateObjectList ::= verb objectList ( ";" verb objectList )* (";")? --}--predicateObjectList :: RDFLabel -> TurtleParser ()-predicateObjectList subj = - let term = verb >>= objectList subj- in sepBy1 term semiColon *> ignore (optional semiColon)- -- in sepBy1 (lexeme term) semiColon *> ignore (optional 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 ::= IRIref - | blank --}--subject :: TurtleParser RDFLabel-subject = (Res <$> iriref) <|> blank--{--[11] predicate ::= IRIref --}--predicate :: TurtleParser RDFLabel-predicate = Res <$> iriref--{--[12] object ::= IRIref - | blank - | literal--}--object :: TurtleParser RDFLabel-object = (Res <$> iriref) <|> blank <|> literal--{--[13] literal ::= RDFLiteral - | NumericLiteral - | BooleanLiteral --}--literal :: TurtleParser RDFLabel-literal = lexeme $ rdfLiteral <|> numericLiteral <|> booleanLiteral--{--[14] blank ::= BlankNode - | blankNodePropertyList - | collection --Since both BlankNode and blankNodePropertyList can match '[ ... ]' we pull-that out and treat this as-- blank ::= BLANK_NODE_LABEL- | "[" (predicateObjectList | WS*) "]"- | collection--blank :: TurtleParser RDFLabel-blank = lexeme (blankNode <|> blankNodePropertyList <|> collection)---}--blank :: TurtleParser RDFLabel-blank = lexeme (_blankNodeLabel- <|>- bracket (char '[') (char ']') handleBlankNode- <|>- collection- )--{--[15] blankNodePropertyList ::= "[" predicateObjectList "]" --We now match the brackets in the parent rule.--blankNodePropertyList :: TurtleParser RDFLabel-blankNodePropertyList = do- bNode <- newBlankNode- -- br "[" "]" (predicateObjectList bNode)- bracket (satisfy (=='['))- (satisfy (==']'))- (_manyws *> predicateObjectList bNode *> _manyws)- -- ignore (optional _manyws) -- TODO: this is a hack- return bNode---}--handleBlankNode :: TurtleParser RDFLabel-handleBlankNode = do- bNode <- newBlankNode- _manyws- ignore $ optional $ predicateObjectList bNode- _manyws- return bNode---{--[16] 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--{--[17] <BASE> ::= "@base" --}--_base :: TurtleParser ()-_base = atWord "base"--{--[18] <PREFIX> ::= "@prefix" --}--_prefix :: TurtleParser ()-_prefix = atWord "prefix"--{--[19] <UCHAR> ::= ( "\\u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] ) - | ( "\\U" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] - [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] ) ---}--_uchar :: TurtleParser Char-_uchar = char '\\' *> _uchar'- -_uchar' :: TurtleParser Char-_uchar' = (char 'u' *> hex4) <|> (char 'U' *> hex8)--{--[60s] RDFLiteral ::= String ( LANGTAG | ( "^^" IRIref ) )? --}--rdfLiteral :: TurtleParser RDFLabel-rdfLiteral = do- lbl <- turtleString- opt <- optional (_langTag <|> (string "^^" *> iriref))- return $ Lit (L.toStrict lbl) opt--{--[61s] NumericLiteral ::= NumericLiteralUnsigned - | NumericLiteralPositive - | NumericLiteralNegative --}--numericLiteral :: TurtleParser RDFLabel-numericLiteral = numericLiteralNegative <|> numericLiteralPositive <|> numericLiteralUnsigned--{--[62s] NumericLiteralUnsigned ::= INTEGER - | DECIMAL - | DOUBLE --}--numericLiteralUnsigned :: TurtleParser RDFLabel-numericLiteralUnsigned = - d2s <$> _double- <|> - (mkTypedLit xsdDecimal . L.toStrict <$> _decimal)- <|> - (mkTypedLit xsdInteger . L.toStrict <$> _integer)--{--[63s] NumericLiteralPositive ::= INTEGER_POSITIVE - | DECIMAL_POSITIVE - | DOUBLE_POSITIVE --}--numericLiteralPositive :: TurtleParser RDFLabel-numericLiteralPositive =- d2s <$> _doublePositive- <|> - (mkTypedLit xsdDecimal . L.toStrict <$> _decimalPositive)- <|> - (mkTypedLit xsdInteger . L.toStrict <$> _integerPositive)--{--[64s] NumericLiteralNegative ::= INTEGER_NEGATIVE - | DECIMAL_NEGATIVE - | DOUBLE_NEGATIVE --}--numericLiteralNegative :: TurtleParser RDFLabel-numericLiteralNegative = - d2s <$> _doubleNegative- <|> - (mkTypedLit xsdDecimal . L.toStrict <$> _decimalNegative)- <|> - (mkTypedLit xsdInteger . L.toStrict <$> _integerNegative)- -{--[65s] BooleanLiteral ::= "true" - | "false"--}--booleanLiteral :: TurtleParser RDFLabel-booleanLiteral = mkTypedLit xsdBoolean . T.pack <$> (string "true" <|> string "false")--{--[66s] String ::= STRING_LITERAL1 - | STRING_LITERAL2 - | STRING_LITERAL_LONG1 - | STRING_LITERAL_LONG2--}--turtleString :: TurtleParser L.Text-turtleString = - lexeme (- _stringLiteralLong1 <|> _stringLiteral1 <|>- _stringLiteralLong2 <|> _stringLiteral2)--{--[67s] IRIref ::= IRI_REF - | PrefixedName --}--iriref :: TurtleParser ScopedName-iriref = lexeme ((makeURIScopedName <$> _iriRef) <|> prefixedName)--{--[68s] PrefixedName ::= PNAME_LN - | PNAME_NS --}--prefixedName :: TurtleParser ScopedName-prefixedName = - _pnameLN <|> - flip makeNSScopedName T.empty <$> (_pnameNS >>= findPrefixNamespace)--{--[69s] BlankNode ::= BLANK_NODE_LABEL - | ANON --blankNode :: TurtleParser RDFLabel-blankNode = lexeme (_blankNodeLabel <|> _anon)---}--{--[70s] <IRI_REF> ::= "<" ( [^<>\"{}|^`\\] - [#0000- ] | UCHAR )* ">" --Read [#0000- ] as [#x00-#x20] from-http://lists.w3.org/Archives/Public/public-rdf-comments/2011Aug/0011.html--Unlike N3, whitespace is significant within the surrounding <>.--At present relying on Network.URI to define what characters are valid-in a URI. This is not necessarily ideal.--}--_iriRef :: TurtleParser URI-_iriRef = do- utxt <- bracket (char '<') (char '>') $ manySatisfy (/= '>') -- TODO: fix- let ustr = L.unpack utxt- case parseURIReference ustr of- Nothing -> fail $ "Unable to convert <" ++ ustr ++ "> to a URI"- Just uref -> do- s <- stGet- either fail return $ appendURIs (baseUri s) uref--{--[71s] <PNAME_NS> ::= (PN_PREFIX)? ":" --}--_pnameNS :: TurtleParser (Maybe L.Text)-_pnameNS = optional _pnPrefix <* char ':'--{--[72s] <PNAME_LN> ::= PNAME_NS PN_LOCAL --}--_pnameLN :: TurtleParser ScopedName-_pnameLN = makeNSScopedName - <$> (_pnameNS >>= findPrefixNamespace) - <*> fmap L.toStrict _pnLocal--{--[73s] <BLANK_NODE_LABEL> ::= "_:" PN_LOCAL --}--_blankNodeLabel :: TurtleParser RDFLabel-_blankNodeLabel = (Blank . L.unpack) <$> (string "_:" *> _pnLocal)--{---These are unused in the grammar.--[74s] <VAR1> ::= "?" VARNAME -[75s] <VAR2> ::= "$" VARNAME --}--{--[76s] <LANGTAG> ::= BASE - | PREFIX - | "@" [a-zA-Z]+ ( "-" [a-zA-Z0-9]+ )* --I am ignoring the BASE and PREFIX lines here as they don't make sense to me.--}--_langTag :: TurtleParser ScopedName-_langTag = do- ichar '@'- h <- many1Satisfy isaZ- mt <- optional (L.cons <$> char '-' <*> many1Satisfy isaZ09)- return $ langName $ L.toStrict $ L.append h (fromMaybe L.empty mt)- -{--[77s] <INTEGER> ::= [0-9]+ --}--_integer :: TurtleParser L.Text-_integer = many1Satisfy is09--{--[78s] <DECIMAL> ::= [0-9]+ "." [0-9]* - | "." [0-9]+ --We try to produce a canonical form for the-numbers.--}--_decimal :: TurtleParser L.Text-_decimal = - let dpart = L.cons <$> char '.' <*> (fromMaybe "0" <$> optional _integer)- in - (L.append <$> _integer <*> dpart)- <|>- (L.append "0." <$> (char '.' *> _integer))--{--[79s] <DOUBLE> ::= [0-9]+ "." [0-9]* EXPONENT - | "." ( [0-9] )+ EXPONENT - | ( [0-9] )+ EXPONENT --Unlike _decimal, the canonical form is enforced-later on, although it could be done here.--}--_double :: TurtleParser L.Text-_double = - (L.append <$> _decimal <*> _exponent)- <|>- (L.append <$> _integer <*> _exponent)- -{--[80s] <INTEGER_POSITIVE> ::= "+" INTEGER -[81s] <DECIMAL_POSITIVE> ::= "+" DECIMAL -[82s] <DOUBLE_POSITIVE> ::= "+" DOUBLE --}--_integerPositive, _decimalPositive, _doublePositive :: TurtleParser L.Text-_integerPositive = char '+' *> _integer-_decimalPositive = char '+' *> _decimal-_doublePositive = char '+' *> _double--{--[83s] <INTEGER_NEGATIVE> ::= "-" INTEGER -[84s] <DECIMAL_NEGATIVE> ::= "-" DECIMAL -[85s] <DOUBLE_NEGATIVE> ::= "-" DOUBLE --}--_integerNegative, _decimalNegative, _doubleNegative :: TurtleParser L.Text-_integerNegative = L.cons <$> char '-' <*> _integer-_decimalNegative = L.cons <$> char '-' <*> _decimal-_doubleNegative = L.cons <$> char '-' <*> _double--{--[86s] <EXPONENT> ::= [eE] [+-]? [0-9]+ --}--_exponent :: TurtleParser L.Text-_exponent = do- ignore $ satisfy (`elem` "eE")- ms <- optional (satisfy (`elem` "+-"))- e <- _integer- case ms of- Just '-' -> return $ L.append "E-" e- _ -> return $ L.cons 'E' e- -{--[87s] <STRING_LITERAL1> ::= "'" ( ( [^'\\\n\r] ) | ECHAR | UCHAR )* "'" -[88s] <STRING_LITERAL2> ::= '"' ( ( [^\"\\\n\r] ) | ECHAR | UCHAR )* '"' -[89s] <STRING_LITERAL_LONG1> ::= "'''" ( ( "'" | "''" )? ( [^'\\] | ECHAR | UCHAR ) )* "'''" -[90s] <STRING_LITERAL_LONG2> ::= '"""' ( ( '"' | '""' )? ( [^\"\\] | ECHAR | UCHAR ) )* '"""' --}--_stringLiteral1, _stringLiteral2 :: TurtleParser L.Text-_stringLiteral1 = _stringIt sQuot (_tChars "'\\\n\r")-_stringLiteral2 = _stringIt dQuot (_tChars "\"\\\n\r")--_stringLiteralLong1, _stringLiteralLong2 :: TurtleParser L.Text-_stringLiteralLong1 = _stringItLong sQuot3 (_tCharsLong '\'' "'\\")-_stringLiteralLong2 = _stringItLong dQuot3 (_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 = (char '\\' *> (_echar' <|> _uchar'))- <|> noneOf excl--_tCharsLong :: Char -> String -> TurtleParser L.Text-_tCharsLong c excl = do- mq <- optional $ oneOrTwo c- r <- _tChars excl- return $ L.append (fromMaybe L.empty mq) (L.singleton r)--oneOrTwo :: Char -> TurtleParser L.Text-oneOrTwo c = do- a <- char c- mb <- optional (char c)- case mb of- Just b -> return $ L.pack [a,b]- _ -> return $ L.singleton a--{--[91s] <ECHAR> ::= "\\" [tbnrf\\\"'] --}--_echar :: TurtleParser Char-_echar = char '\\' *> _echar'--_echar' :: TurtleParser Char-_echar' = - (char 't' *> pure '\t') <|>- (char 'b' *> pure '\b') <|>- (char 'n' *> pure '\n') <|>- (char 'r' *> pure '\r') <|>- (char '\\' *> pure '\\') <|>- (char '"' *> pure '"') <|>- (char '\'' *> pure '\'')---{---Unused.--[92s] <NIL> ::= "(" (WS)* ")" --}--{--[93s] <WS> ::= " " - | "\t" - | "\r" - | "\n"--_ws :: TurtleParser ()-_ws = ignore $ satisfy (`elem` " \t\r\n")---}--_manyws :: TurtleParser ()-_manyws = ignore $ manySatisfy (`elem` " \t\r\n")--{--[94s] <ANON> ::= "[" (WS)* "]" --Unused as we do not support the use of ANON in the BlankNode-terminal.--_anon :: TurtleParser RDFLabel-_anon = br "[" "]" _manyws *> newBlankNode---}--{--[95s] <PN_CHARS_BASE> ::= [A-Z] - | [a-z] - | [#00C0-#00D6] - | [#00D8-#00F6] - | [#00F8-#02FF] - | [#0370-#037D] - | [#037F-#1FFF] - | [#200C-#200D] - | [#2070-#218F] - | [#2C00-#2FEF] - | [#3001-#D7FF] - | [#F900-#FDCF] - | [#FDF0-#FFFD] - | [#10000-#EFFFF] - | UCHAR --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 <|> _uchar--{--[96s] <PN_CHARS_U> ::= PN_CHARS_BASE - | "_"--}--_pnCharsU :: TurtleParser Char-_pnCharsU = _pnCharsBase <|> char '_'--{---Only used in VAR1/2 rules which are themselves unused.--Unused in the grammar (other than-[97s] <VARNAME> ::= ( PN_CHARS_U | [0-9] ) ( PN_CHARS_U | [0-9] | #00B7 | [#0300-#036F] | [# - 203F-#2040] )* --}--{--[98s] <PN_CHARS> ::= PN_CHARS_U - | "-" - | [0-9] - | #00B7 - | [#0300-#036F] - | [#203F-#2040] --}--_pnChars :: TurtleParser Char-_pnChars = - _pnCharsU - <|> - satisfy (\c -> let i = ord c - in c == '-' || (c >= '0' && c <= '9') || i == 0xb7 ||- match i [(0x0300, 0x036f), (0x203f, 0x2040)])--{--[99s] <PN_PREFIX> ::= PN_CHARS_BASE ( ( PN_CHARS | "." )* PN_CHARS )? --}--_pnPrefix :: TurtleParser L.Text-_pnPrefix = L.cons <$> _pnCharsBase <*> _pnRest- -{--[100s] <PN_LOCAL> ::= ( PN_CHARS_U | [0-9] ) ( ( PN_CHARS | "." )* PN_CHARS )? --} --_pnLocal :: TurtleParser L.Text-_pnLocal = L.cons <$> (_pnCharsU <|> satisfy is09) - <*> _pnRest--{--Extracted from PN_PREFIX and PN_LOCAL is--<PN_REST> :== ( ( PN_CHARS | "." )* PN_CHARS )?--We assume below that the match is only ever done for small strings, so-the cost of the foldr isn't likely to be large. Let's see how well-this assumption holds up.---}--_pnRest :: TurtleParser L.Text-_pnRest = do- lbl <- many (_pnChars <|> char '.')- let (nret, lclean) = clean 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) (L.singleton '.')- return $ L.pack lclean--{--Original from -- chop = go 0 []- where- -- go :: State -> Stack -> String -> String- go 0 _ [] = []- go 0 _ (x:xs)- | isSpace x = go 1 [x] xs- | otherwise = x : go 0 xs-- go 1 ss [] = []- go 1 ss (x:xs)- | isSpace c = go 1 (x:ss) xs- | otherwise = reverse ss ++ x : go 0 xs---}---------------------------------------------------------------------------------------- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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/RDF/VarBinding.hs
@@ -1,540 +0,0 @@-{-# LANGUAGE MultiParamTypeClasses, TypeSynonymInstances, OverloadedStrings #-}-{-# LANGUAGE FlexibleInstances #-} -- needed for ghc 7.12------------------------------------------------------------------------------------- See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : VarBinding--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke--- License : GPL V2------ Maintainer : Douglas Burke--- Stability : experimental--- Portability : MultiParamTypeClasses, TypeSynonymInstances, OverloadedStrings, FlexibleInstances------ This module defines functions for representing and manipulating query--- binding variable sets. This is the key data that mediates between--- query and back substitution when performing inferences. A framework--- of query variable modifiers is provided that can be used to--- implement richer inferences, such as filtering of query results,--- or replacing values based on known relationships.--------------------------------------------------------------------------------------module Swish.RDF.VarBinding- ( VarBinding(..), nullVarBinding- , boundVars, subBinding, makeVarBinding- , applyVarBinding, joinVarBindings, addVarBinding- , VarBindingModify(..), OpenVarBindingModify- , vbmCompatibility, vbmCompose- , composeSequence, findCompositions, findComposition- , VarBindingFilter(..)- , makeVarFilterModify- , makeVarTestFilter, makeVarCompareFilter- , varBindingId, nullVarBindingModify- , varFilterDisjunction, varFilterConjunction- , varFilterEQ, varFilterNE- )-where--import Swish.Utils.LookupMap- ( LookupEntryClass(..) - , makeLookupMap, mapFindMaybe- )--import Swish.Utils.Namespace (ScopedName, getScopeLocal)-import Swish.RDF.Vocabulary (swishName)-import Swish.Utils.ListHelpers (equiv, subset, flist, headOrNothing, permutations)--import Data.Maybe (mapMaybe, fromMaybe, isJust, fromJust, listToMaybe)--import Data.List (find, intersect, union, (\\), foldl')--import Data.Monoid (mconcat)---- import qualified Data.Text as T----------------------------------------------------------------- Query variable bindings----------------------------------------------------------------- TODO: is it worth making a Monoid instance of VarBinding?---- |VarBinding is the type of an arbitrary variable bindings--- value, where the type of the bound values is not specified.----data VarBinding a b = VarBinding- { vbMap :: a -> Maybe b- , vbEnum :: [(a,b)]- , vbNull :: Bool- }---- |VarBinding is an instance of class Eq, so that variable--- bindings can be compared for equivalence----instance (Eq a, Eq b) => Eq (VarBinding a b) where- vb1 == vb2 = vbEnum vb1 `equiv` vbEnum vb2---- |VarBinding is an instance of class Show, so that variable--- bindings can be displayed----instance (Show a, Show b) => Show (VarBinding a b) where- show = show . vbEnum---- | maps no query variables.----nullVarBinding :: VarBinding a b-nullVarBinding = VarBinding- { vbMap = const Nothing- , vbEnum = []- , vbNull = True- }---- |Return a list of the variables bound by a supplied variable binding----boundVars :: VarBinding a b -> [a]-boundVars = map fst . vbEnum---- |VarBinding subset function, tests to see if one query binding--- is a subset of another; i.e. every query variable mapping defined--- by one is also defined by the other.----subBinding :: (Eq a, Eq b) => VarBinding a b -> VarBinding a b -> Bool-subBinding vb1 vb2 = vbEnum vb1 `subset` vbEnum vb2---- |Function to make a variable binding from a list of--- pairs of variable and corresponding assigned value.----makeVarBinding :: (Eq a, Show a, Eq b, Show b) => [(a,b)] -> VarBinding a b-makeVarBinding vrbs =- if null vrbs then nullVarBinding -- (nullVarBinding :: VarBinding a b)- else VarBinding- { vbMap = selectFrom vrbs- , vbEnum = vrbs- , vbNull = null vrbs- }- where- selectFrom = flip mapFindMaybe . makeLookupMap- -- selectFrom bs is the VarBinding lookup function- {-- selectFrom :: (Eq a) => [(a,b)] -> a -> Maybe b- selectFrom [] _ = Nothing- selectFrom ((v,r):bs) l = if l == v then Just r- else selectFrom bs l- -}---- |Apply query binding to a supplied value, returning the value--- unchanged if no binding is defined----applyVarBinding :: VarBinding a a -> a -> a-applyVarBinding vbind v = fromMaybe v (vbMap vbind v)---- |Join a pair of query bindings, returning a new binding that--- maps all variables recognized by either of the input bindings.--- If the bindings should overlap, such overlap is not detected and--- the value from the first binding provided is used arbitrarily.----joinVarBindings :: (Eq a) => VarBinding a b -> VarBinding a b -> VarBinding a b-joinVarBindings vb1 vb2- | vbNull vb1 = vb2- | vbNull vb2 = vb1- | otherwise = VarBinding- { vbMap = mv12- , vbEnum = map (\v -> (v,fromJust (mv12 v))) bv12- , vbNull = False- }- where- -- flist fs a = map ($ a) fs; see also monad function 'ap'- mv12 = headOrNothing . filter isJust . flist [ vbMap vb1, vbMap vb2 ]- bv12 = boundVars vb1 `union` boundVars vb2---- |Add a single new value to a variable binding and return the resulting--- new variable binding.----addVarBinding :: (Eq a, Show a, Eq b, Show b) => a -> b -> VarBinding a b- -> VarBinding a b-addVarBinding lb val vbind = joinVarBindings vbind $ makeVarBinding [(lb,val)]----------------------------------------------------------------- Datatypes for variable binding modifiers----------------------------------------------------------------- |Define the type of a function to modify variable bindings in--- forward chaining based on rule antecedent matches. This--- function is used to implement the \"allocated to\" logic described--- in Appendix B of the RDF semantics document, in which a specific--- blank node is associated with all matches of some specific value--- by applications of the rule on a given graph.--- Use 'id' if no modification of the variable bindings is required.------ This datatype consists of the modifier function itself, which--- operates on a list of variable bindings rather than a single--- variable binding (because some modifications share context across--- a set of bindings), and some additional descriptive information--- that allows possible usage patterns to be analyzed.------ Some usage patterns (see 'vbmUsage' for more details):------ [filter] all variables are input variables, and the effect--- of the modifier function is to drop variable bindings that--- don't satisfy some criterion.--- Identifiable by an empty element in @vbmUsage@.------ [source] all variables are output variables: a raw query--- could be viewed as a source of variable bindings.--- Identifiable by an element of @vbmUsage@ equal to @vbmVocab@.------ [modifier] for each supplied variable binding, one or more--- new variable bindings may be created that contain the--- input variables bound as supplied plus some additional variables.--- Identifiable by an element of @vbmUsage@ some subset of @vbmVocab@.------ A variety of variable usage patterns may be supported by a given--- modifier: a modifier may be used to define new variable bindings--- from existing bindings in a number of ways, or simply to check that--- some required relationship between bindings is satisfied.--- (Example, for @a + b = c@, any one variable can be deduced from the--- other two, or all three may be supplied to check that the relationship--- does indeed hold.)----data VarBindingModify a b = VarBindingModify- { vbmName :: ScopedName- -- ^Name used to identify this variable binding- -- modifier when building inference rules.- , vbmApply :: [VarBinding a b] -> [VarBinding a b]- -- ^Apply variable binding modifier to a- -- list of variable bindings, returning a- -- new list. The result list is not- -- necessarily the same length as the- -- supplied list.- , vbmVocab :: [a] -- ^List of variables used by this modifier.- -- All results of applying this modifier contain- -- bindings for these variables.- , vbmUsage :: [[a]] -- ^List of binding modifier usage patterns- -- supported. Each pattern is characterized as- -- a list of variables for which new bindings- -- may be created by some application of this- -- modifier, assuming that bindings for all other- -- variables in @vbmVocab@ are supplied.- }---- |Allow a VarBindingModify value to be accessed using a 'LookupMap'.----instance LookupEntryClass- (VarBindingModify a b) ScopedName (VarBindingModify a b)- where- keyVal vbm = (vbmName vbm,vbm)- newEntry (_,vbm) = vbm---- |Type for variable binding modifier that has yet to be instantiated--- with respect to the variables that it operates upon.----type OpenVarBindingModify lb vn = [lb] -> VarBindingModify lb vn---- |Extract variable binding name from @OpenVarBindingModify@ value------ (Because only the name is required, the application to an undefined--- list of variable labels should never be evaluated, as long as the--- name is not dependent on the variable names in any way.)------ NOT QUITE... some of the functions that create @OpenVarBindingModify@--- instances also pattern-match the number of labels provided, forcing--- evaluation of the labels parameter, even though it's not used.----openVbmName :: OpenVarBindingModify lb vn -> ScopedName-openVbmName ovbm = vbmName (ovbm (error "Undefined labels in variable binding"))---- |Allow an @OpenVarBindingModify@ value to be accessed using a @LookupMap@.----instance LookupEntryClass- (OpenVarBindingModify a b) ScopedName (OpenVarBindingModify a b)- where- keyVal ovbm = (openVbmName ovbm,ovbm)- newEntry (_,ovbm) = ovbm---- |Allow an OpenVarBindingModify value to be accessed using a LookupMap.----instance Show (OpenVarBindingModify a b)- where- show = show . openVbmName---- |Variable binding modifier compatibility test.------ Given a list of bound variables and a variable binding modifier, return--- a list of new variables that may be bound, or @Nothing@.------ Note: if the usage pattern component is well-formed (i.e. all--- elements different) then at most one element can be compatible with--- a given input variable set.----vbmCompatibility :: (Eq a) => VarBindingModify a b -> [a] -> Maybe [a]-vbmCompatibility vbm vars = find compat (vbmUsage vbm)- where- compat = vbmCompatibleVars vars (vbmVocab vbm)---- |Variable binding usage compatibility test.------ Returns @True@ if the supplied variable bindings can be compatibly--- processed by a variable binding usage with supplied vocabulary and--- usage pattern.----vbmCompatibleVars ::- (Eq a) - => [a] -- ^ variables supplied with bindings- -> [a] -- ^ variables returned with bindings by a modifier- -> [a] -- ^ variables assigned new bindings by a modifier- -> Bool-vbmCompatibleVars bvars vocab ovars =- null (ivars `intersect` ovars) && -- ivars and ovars don't overlap- null ((vocab \\ ovars) \\ ivars) -- ovars and ivars cover vocab- where- ivars = bvars `intersect` vocab---- |Compose variable binding modifiers.------ Returns @Just a@ new variable binding modifier that corresponds to--- applying the first supplied modifier and then applying the second--- one, or @Nothing@ if the two modifiers cannot be compatibly composed.------ NOTE: this function does not, in general, commute.------ NOTE: if there are different ways to achieve the same usage, that--- usage is currently repeated in the result returned.----vbmCompose :: (Eq a) => VarBindingModify a b -> VarBindingModify a b- -> Maybe (VarBindingModify a b)-vbmCompose- (VarBindingModify nam1 app1 voc1 use1)- (VarBindingModify nam2 app2 voc2 use2)- | not (null use12) = Just VarBindingModify- { vbmName = swishName $ mconcat ["_", getScopeLocal nam1, "_", getScopeLocal nam2, "_"]- , vbmApply = app2 . app1- , vbmVocab = voc1 `union` voc2- , vbmUsage = use12- }- | otherwise = Nothing- where- use12 = compatibleUsage voc1 use1 use2---- |Determine compatible ways in which variable binding modifiers may--- be combined.------ The total vocabulary of a modifier is the complete set of variables--- that are used or bound by the modifier. After the modifier has been--- applied, bindings must exist for all of these variables.------ A usage pattern of a modifier is a set of variables for which new--- bindings may be generated by the modifier.------ The only way in which two variable binding modifiers can be incompatible--- with each other is when they both attempt to create a new binding for--- the same variable. (Note that this does not mean the composition will--- be compatible with all inputs: see 'vbmCompatibleVars'.)------ NOTE: if there are different ways to achieve the same usage, that--- usage is currently repeated in the result returned.----compatibleUsage ::- (Eq a)- => [a] -- ^ the total vocabulary of the first modifier to be applied- -> [[a]] -- ^ usage patterns for the first modifier- -> [[a]] -- ^ usage patterns for the second modifier- -> [[a]] -- ^ a list of possible usage patterns for the composition of- -- the first modifier with the second modifier, or an empty list if- -- the modifiers are incompatible.-compatibleUsage voc1 use1 use2 =- [ u1++u2 | u2 <- use2, null (voc1 `intersect` u2), u1 <- use1 ]---- |Find all compatible compositions of a list of variable binding--- modifiers for a given set of supplied bound variables.-findCompositions :: (Eq a) => [VarBindingModify a b] -> [a]- -> [VarBindingModify a b]-findCompositions vbms vars =- mapMaybe (composeCheckSequence vars) (permutations vbms)---- |Compose sequence of variable binding modifiers, and check--- that the result can be used compatibly with a supplied list--- of bound variables, returning @Just (composed modifier)@,--- or @Nothing@.----composeCheckSequence :: (Eq a) => [a] -> [VarBindingModify a b]- -> Maybe (VarBindingModify a b)-composeCheckSequence vars vbms = useWith vars $ composeSequence vbms- where- -- Check that a Maybe modifier is compatible for use with an- -- indicated set of bound variables, and return (Just modifier)- -- or Nothing.- useWith _ Nothing = Nothing- useWith vs v@(Just vbm)- | isJust $ vbmCompatibility vbm vs = v- | otherwise = Nothing---- |Compose sequence of variable binding modifiers.----composeSequence :: (Eq a) => [VarBindingModify a b]- -> Maybe (VarBindingModify a b)-composeSequence [] = Just varBindingId-composeSequence (vbm:vbms) = foldl' composePair (Just vbm) vbms---- |Compose a pair of variable binding modifiers, returning--- @Just (composed modifier)@, or @Nothing@.----composePair :: (Eq a) => Maybe (VarBindingModify a b) -> VarBindingModify a b- -> Maybe (VarBindingModify a b)-composePair Nothing _ = Nothing-composePair (Just vbm1) vbm2 = vbmCompose vbm1 vbm2---- |Return @Just a@ compatible composition of variable binding modifiers--- for a given set of supplied bound variables, or @Nothing@ if there--- is no compatible composition----findComposition :: (Eq a) => [VarBindingModify a b] -> [a]- -> Maybe (VarBindingModify a b)-findComposition = listToMaybe `c2` findCompositions- where- c2 = (.) . (.) -- compose with function of two arguments---- |Variable binding modifier that returns exactly those--- variable bindings presented.----varBindingId :: VarBindingModify a b-varBindingId = VarBindingModify- { vbmName = swishName "varBindingId"- , vbmApply = id- , vbmVocab = []- , vbmUsage = [[]]- }---- |Null variable binding modifier------ This is like 'varBindingId' except parameterized by some labels.--- I think this is redundant, and should be eliminated.----nullVarBindingModify :: OpenVarBindingModify a b-nullVarBindingModify lbs = VarBindingModify- { vbmName = swishName "nullVarBindingModify"- , vbmApply = id- , vbmVocab = lbs- , vbmUsage = [[]]- }----------------------------------------------------------------- Query binding filters----------------------------------------------------------------- |VarBindingFilter is a function type that tests to see if--- a query binding satisfies some criterion.------ 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 below.-data VarBindingFilter a b = VarBindingFilter- { vbfName :: ScopedName- , vbfVocab :: [a]- , vbfTest :: VarBinding a b -> Bool- }---- |Make a variable binding modifier from a variable binding filter value.-makeVarFilterModify :: VarBindingFilter a b -> VarBindingModify a b-makeVarFilterModify vbf = VarBindingModify- { vbmName = vbfName vbf- , vbmApply = filter (vbfTest vbf)- , vbmVocab = vbfVocab vbf- , vbmUsage = [[]]- }---- |Make a variable test filter for a named variable using a--- supplied value testing function.-makeVarTestFilter ::- ScopedName -> (b -> Bool) -> a -> VarBindingFilter a b-makeVarTestFilter nam vtest var = VarBindingFilter- { vbfName = nam- , vbfVocab = [var]- , vbfTest = \vb -> case vbMap vb var of- Just val -> vtest val- _ -> False- }---- |Make a variable comparison filter for named variables using--- a supplied value comparison function.-makeVarCompareFilter ::- ScopedName -> (b -> b -> Bool) -> a -> a -> VarBindingFilter a b-makeVarCompareFilter nam vcomp v1 v2 = VarBindingFilter- { vbfName = nam- , vbfVocab = [v1,v2]- , vbfTest = \vb -> case (vbMap vb v1,vbMap vb v2) of- (Just val1, Just val2) -> vcomp val1 val2- _ -> False- }----------------------------------------------------------------- Declare some generally useful query binding filters----------------------------------------------------------------- |This function generates a query binding filter that ensures that--- two indicated query variables are mapped to the same value.-varFilterEQ :: (Eq b) => a -> a -> VarBindingFilter a b-varFilterEQ =- makeVarCompareFilter (swishName "varFilterEQ") (==) ---- |This function generates a query binding filter that ensures that--- two indicated query variables are mapped to different values.-varFilterNE :: (Eq b) => a -> a -> VarBindingFilter a b-varFilterNE =- makeVarCompareFilter (swishName "varFilterNE") (/=) ---- |This function composes a number of query binding filters--- into a composite filter that accepts any query binding that--- satisfies at least one of the component values.-varFilterDisjunction :: (Eq a) => [VarBindingFilter a b]- -> VarBindingFilter a b-varFilterDisjunction vbfs = VarBindingFilter- { vbfName = swishName "varFilterDisjunction"- , vbfVocab = foldl1 union (map vbfVocab vbfs)- , vbfTest = or . flist (map vbfTest vbfs)- }---- |This function composes a number of query binding filters--- into a composite filter that accepts any query binding that--- satisfies all of the component values.------ The same function could be achieved by composing the component--- filter-based modifiers, but this function is more convenient--- as it avoids the need to check for modifier compatibility.----varFilterConjunction :: (Eq a) => [VarBindingFilter a b]- -> VarBindingFilter a b-varFilterConjunction vbfs = VarBindingFilter- { vbfName = swishName "varFilterConjunction"- , vbfVocab = foldl1 union (map vbfVocab vbfs)- , vbfTest = and . flist (map vbfTest vbfs)- }---------------------------------------------------------------------------------------- (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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/RDF/Vocabulary.hs
@@ -1,264 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}------------------------------------------------------------------------------------- See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : Vocabulary--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke--- License : GPL V2------ Maintainer : Douglas Burke--- Stability : experimental--- Portability : OverloadedStrings------ This module defines some commonly used vocabulary terms,--- using the 'Namespace' and 'ScopedName' data types.--------------------------------------------------------------------------------------module Swish.RDF.Vocabulary- ( namespaceRDF- , namespaceRDFS- , namespaceRDFD- , namespaceOWL- , namespaceXSD- , namespaceXsdType- , namespaceMATH- , namespaceLOG- , namespaceDAML- , namespaceDefault- , namespaceSwish - , swishName- , namespaceLang, langName, langTag, isLang- , scopeRDF- , scopeRDFS- , scopeRDFD- , rdfDatatype, rdfResource, rdfAbout, rdfID- , rdfType- , rdfFirst, rdfRest, rdfNil, rdfXMLLiteral- , rdfsMember- , rdfdGeneralRestriction- , rdfdOnProperties, rdfdConstraint, rdfdMaxCardinality- , owlSameAs, logImplies- , xsdType, xsdString, xsdBoolean- , xsdDecimal, xsdInteger- , xsdNonNegInteger, xsdNonPosInteger, xsdPosInteger, xsdNegInteger- , xsdFloat, xsdDouble- , xsdDate, xsdDateTime- , defaultBase- )-where--import Swish.Utils.Namespace (Namespace, makeNamespace, ScopedName, getScopeLocal, getScopeNamespace, makeScopedName)--import Data.Monoid (mappend, mconcat)-import Data.Maybe (fromMaybe)-import Network.URI (URI, parseURI)--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)--namespaceXsdType :: T.Text -> Namespace-namespaceXsdType dtn = toNS ("xsd_" `mappend` dtn)- (mconcat ["http://id.ninebynine.org/2003/XMLSchema/", dtn, "#"])--namespaceRDF :: Namespace-namespaceRDFS :: Namespace-namespaceRDFD :: Namespace-namespaceOWL :: Namespace-namespaceXSD :: Namespace-namespaceMATH :: Namespace-namespaceLOG :: Namespace-namespaceDAML :: Namespace-namespaceSwish :: Namespace-namespaceDefault :: Namespace-namespaceLang :: Namespace--namespaceRDF = toNSU "rdf" namespaceRDFURI-namespaceRDFS = toNSU "rdfs" namespaceRDFSURI-namespaceRDFD = toNSU "rdfd" namespaceRDFDURI-namespaceOWL = toNSU "owl" namespaceOWLURI-namespaceXSD = toNSU "xsd" namespaceXSDURI-namespaceMATH = toNS "math" "http://www.w3.org/2000/10/swap/math#"-namespaceLOG = toNSU "log" namespaceLOGURI-namespaceDAML = toNS "daml" "http://www.daml.org/2000/10/daml-ont#"-namespaceSwish = toNSU "swish" namespaceSwishURI-namespaceLang = toNSU "lang" namespaceLangURI-namespaceDefault = toNSU "default" namespaceDefaultURI--tU :: String -> URI-tU = fromMaybe (error "Internal error processing namespace URI") . parseURI--namespaceRDFURI, namespaceRDFSURI, namespaceRDFDURI, - namespaceXSDURI, namespaceOWLURI, namespaceLOGURI,- namespaceSwishURI, - namespaceLangURI, namespaceDefaultURI :: URI-namespaceRDFURI = tU "http://www.w3.org/1999/02/22-rdf-syntax-ns#"-namespaceRDFSURI = tU "http://www.w3.org/2000/01/rdf-schema#"-namespaceRDFDURI = tU "http://id.ninebynine.org/2003/rdfext/rdfd#"-namespaceOWLURI = tU "http://www.w3.org/2002/07/owl#"-namespaceXSDURI = tU "http://www.w3.org/2001/XMLSchema#"-namespaceLOGURI = tU "http://www.w3.org/2000/10/swap/log#"-namespaceSwishURI = tU "http://id.ninebynine.org/2003/Swish/"-namespaceLangURI = tU "http://id.ninebynine.org/2003/Swish/Lang/" -- To be replaced by urn:ietf:params:lang? -namespaceDefaultURI = tU "http://id.ninebynine.org/default/"--swishName :: T.Text -> ScopedName--- swishName = ScopedName namespaceSwish-swishName = makeScopedName (Just "swish") namespaceSwishURI---------------------------------------------------------------- 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).--langName :: T.Text -> ScopedName--- langName = ScopedName namespaceLang . T.toLower-langName = makeScopedName (Just "lang") namespaceLangURI . T.toLower--langTag :: ScopedName -> T.Text-langTag = getScopeLocal--isLang :: ScopedName -> Bool-isLang sname = getScopeNamespace sname == namespaceLang----------------------------------------------------------------- Define namespaces for RDF rules, axioms, etc---------------------------------------------------------------scopeRDF, scopeRDFS, scopeRDFD :: Namespace--scopeRDF = toNS "rs_rdf" "http://id.ninebynine.org/2003/Ruleset/rdf#"-scopeRDFS = toNS "rs_rdfs" "http://id.ninebynine.org/2003/Ruleset/rdfs#"-scopeRDFD = toNS "rs_rdfd" "http://id.ninebynine.org/2003/Ruleset/rdfd#"----------------------------------------------------------------- Define some common vocabulary terms---------------------------------------------------------------toRDF, toRDFS, toRDFD :: T.Text -> ScopedName-toRDF = makeScopedName (Just "rdf") namespaceRDFURI-toRDFS = makeScopedName (Just "rdfs") namespaceRDFSURI-toRDFD = makeScopedName (Just "rdfd") namespaceRDFDURI--rdfDatatype :: ScopedName-rdfResource :: ScopedName-rdfAbout :: ScopedName-rdfID :: ScopedName-rdfType :: ScopedName-rdfFirst :: ScopedName-rdfRest :: ScopedName-rdfNil :: ScopedName-rdfXMLLiteral :: ScopedName--rdfDatatype = toRDF "datatype"-rdfResource = toRDF "resource"-rdfAbout = toRDF "about"-rdfID = toRDF "ID"-rdfType = toRDF "type"-rdfFirst = toRDF "first"-rdfRest = toRDF "rest"-rdfNil = toRDF "nil"-rdfXMLLiteral = toRDF "XMLLiteral"--rdfsMember :: ScopedName-rdfsMember = toRDFS "member"--rdfdGeneralRestriction :: ScopedName-rdfdOnProperties :: ScopedName-rdfdConstraint :: ScopedName-rdfdMaxCardinality :: ScopedName--rdfdGeneralRestriction = toRDFD "GeneralRestriction"-rdfdOnProperties = toRDFD "onProperties"-rdfdConstraint = toRDFD "constraint"-rdfdMaxCardinality = toRDFD "maxCardinality"--xsdType :: T.Text -> ScopedName-xsdType = makeScopedName (Just "xsd") namespaceXSDURI--xsdString :: ScopedName-xsdString = xsdType "string"--xsdBoolean :: ScopedName-xsdBoolean = xsdType "boolean"--xsdDecimal :: ScopedName-xsdDecimal = xsdType "decimal"--xsdInteger :: ScopedName-xsdInteger = xsdType "integer"--xsdNonNegInteger :: ScopedName-xsdNonNegInteger = xsdType "nonNegativeInteger"--xsdNonPosInteger :: ScopedName-xsdNonPosInteger = xsdType "nonPositiveInteger"--xsdPosInteger :: ScopedName-xsdPosInteger = xsdType "positiveInteger"--xsdNegInteger :: ScopedName-xsdNegInteger = xsdType "negativeInteger"--xsdFloat :: ScopedName-xsdFloat = xsdType "float"--xsdDouble :: ScopedName-xsdDouble = xsdType "double"--xsdDate, xsdDateTime :: ScopedName-xsdDate = xsdType "date"-xsdDateTime = xsdType "dateTime"--owlSameAs :: ScopedName-owlSameAs = makeScopedName (Just "owl") namespaceOWLURI "sameAs"--logImplies :: ScopedName-logImplies = makeScopedName (Just "log") namespaceLOGURI "implies"--defaultBase :: ScopedName-defaultBase = makeScopedName (Just "default") namespaceDefaultURI "base"---------------------------------------------------------------------------------------- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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/Utils/ListHelpers.hs
@@ -1,359 +0,0 @@------------------------------------------------------------------------------------ See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : ListHelpers--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke--- License : GPL V2------ Maintainer : Douglas Burke--- Stability : experimental--- Portability : H98------ This module defines some generic list and related helper functions. --------------------------------------------------------------------------------------{--TODO:--The plan is to use modules such as Data.Set where appropriate.---}--module Swish.Utils.ListHelpers- ( -- list of Swish.RDF.xxx modules the routine is used in- select -- GraphMatch- , deleteIndex -- Datatype- , subset -- Proof, RDFProof, VarBinding [also defined in Utils.PartOrderedCollection]- , equiv -- GraphMatch, RDFRuleset, SwishScript, VarBinding, Utils.LookupMap- , addSetElem -- RDFGraph- , headOrNothing -- VarBinding- , pairUngroup -- GraphMatch- , pairSort -- GraphMatch- , pairGroup -- GraphMatch- , breakAll -- SwishMain- , powerSet -- ClassRestrictionRule, RDFProof- , permutations -- VarBinding- , listProduct -- RDFQuery- , powerSequencesLen -- RDFProof- , flist -- Datatype, RDFProof, RDFRuleset, SwishScript, VarBinding- , allp -- RDFQuery- , anyp -- RDFQuery- - )-where- -import Data.Ord (comparing) -import Data.List (sortBy, groupBy)----------------------------------------------------------------- Generic helpers----------------------------------------------------------------- |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"---- |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 ]--}----------------------------------------------------------------- Set functions------ NOTE: to change to Data.Set then Eq a constraint will --- likely need changing to Ord a----------------------------------------------------------------- |Subset test--subset :: (Eq a) => [a] -> [a] -> Bool-a `subset` b = and [ ma `elem` b | ma <- a ]---- |Set equivalence test--equiv :: (Eq a) => [a] -> [a] -> Bool-a `equiv` b = a `subset` b && b `subset` a---- |Add element to set--addSetElem :: (Eq a) => a -> [a] -> [a]-addSetElem e es = if e `elem` es then es else e:es----------------------------------------------------------------- Lists and Maybes----------------------------------------------------------------- |Return head of a list of @Maybe@'s, or @Nothing@ if list is empty------ Use with @filter isJust@ to select a non-Nothing value from a--- list when such a value is present.----headOrNothing :: [Maybe a] -> Maybe a-headOrNothing [] = Nothing-headOrNothing (a:_) = a----------------------------------------------------------------- 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)--}--pairUngroup :: (a,[b]) -> [(a,b)]-pairUngroup (a,bs) = [ (a,b) | b <- bs ]--pairSort :: (Ord a) => [(a,b)] -> [(a,b)]-pairSort = sortBy (comparing fst)--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 a b = fst a == fst b----------------------------------------------------------------- Separate list into sublists----------------------------------------------------------------- |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')----------------------------------------------------------------- Powerset----------------------------------------------------------------- [[[TBD... there's a much better implementation in my email,--- from Christopher Hendrie. This is the raw code.]]]-{-->ranked_powerset :: [a] -> [[[a]]]->ranked_powerset = takeWhile (not . null) . foldr next_powerset ([[]] :-repeat [])->->next_powerset :: a -> [[[a]]] -> [[[a]]]->next_powerset x r = zipWith (++) ([] : map (map (x:)) r) r->->powerset :: [a] -> [[a]]->powerset = tail . concat . ranked_powerset--}---- |Powerset of a list, in ascending order of size.--- Assumes the supplied list has no duplicate elements.-powerSet :: [a] -> [[a]]-powerSet as =- concatMap (`combinations` as) [1..length as]---- |Combinations of n elements from a list, each being returned in the--- order that they appear in the list.-combinations :: Int -> [a] -> [[a]]-combinations _ [] = [] -- Don't include empty combinations-combinations n as@(ah:at)- | n <= 0 = [[]]- | n > length as = []- | n == length as = [as]- | otherwise = map (ah:) (combinations (n-1) at) ++- combinations n at--{---- |Return list of integers from lo to hi.-intRange :: Int -> Int -> [Int]-intRange lo hi = take (hi-lo+1) (iterate (+1) 1)--}--{---- Tests-testcomb0 = combinations 0 "abcd" -- []-testcomb1 = combinations 1 "abcd" -- ["a","b","c","d"]-testcomb2 = combinations 2 "abcd" -- ["ab","ac","ad","bc","bd","cd"]-testcomb3 = combinations 3 "abcd" -- ["abc","abd","acd","bcd"]-testcomb4 = combinations 4 "abcd" -- ["abcd"]-testcomb5 = combinations 5 "abcd" -- []-testpower = powerSet "abc" -- ["a","b","c","ab","ac","bc","abc"]--}----------------------------------------------------------------- Permutations of a list----------------------------------------------------------------- This algorithm is copied from an email by S.D.Mechveliani--- http://www.dcs.gla.ac.uk/mail-www/haskell/msg01936.html-permutations :: [a] -> [[a]]-permutations [] = [[]]-permutations (j:js) = addOne $ permutations js- where- addOne = foldr ((++) . ao) []-- ao [] = [[j]]- ao (k:ks) = (j:k:ks) : map (k:) (ao ks)--{--testperm = permutations [1,2,3] ==- [[1,2,3],[2,1,3],[2,3,1],[1,3,2],[3,1,2],[3,2,1]]--}----------------------------------------------------------------- List product----------------------------------------------------------------- |Given a list of lists, construct a new list of lists where--- each member of the new list is the same length as the original--- list, and each member corresponds to a different choice of--- one element from each of the original members in the--- corresponding position. Thus:------ > listProduct [[a1,a2],[b1],[c1,c2]] =--- > [ [a1,b1,c1], [a1,b1,c2], [a2,b1,c1], [a2,b1,c2] ]------ Note: The length of the resulting list is the prodicty of--- lengths of the components of the original list. Thus, if--- any member of the original list is empty then so is the--- resulting list:------ > listProduct [[a1,a2],[],[c1,c2]] = []------ NOTE: this is subsumed by 'sequence'----listProduct :: [[a]] -> [[a]]-listProduct [] = [[]]-listProduct (as:ass) = concat [ map (a:) (listProduct ass) | a <- as ]--{--test1 = listProduct [["a1","a2"],["b1"],["c1","c2"]]-test2 = listProduct [["a1","a2"],[],["c1","c2"]]--lp [] = [[]]-lp (as:ass) = concatMap (\a -> (map (a:) (lp ass))) as--}----------------------------------------------------------------- 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----------------------------------------------------------------- Functions, lists and monads----------------------------------------------------------------- |Apply list of functions to some value, returning list of results.--- It's kind of like an converse map.------ This is similar to the 'ap' function in the Monad library.----flist :: [a->b] -> a -> [b]-flist fs a = map ($ a) fs--{--flisttest = flist [(1*),(2*),(3*)] 5 -- [5,10,15]--}--{----- |A more generalized form of flist that works with arbitrary Monads.--- (Suggested by Derek Elkin.)--fmonad :: Monad m => m (a->b) -> a -> m b-fmonad fm a =- do { f <- fm- ; return $ f a- }---}--{--fmonadtest = fmonad [(1*),(2*),(3*)] 3 -- [3,6,9]--}---- |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]--}----------------------------------------------------------------------------------------- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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/Utils/LookupMap.hs
@@ -1,454 +0,0 @@-{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}-{-# LANGUAGE FlexibleContexts, FlexibleInstances, TypeSynonymInstances #-}-{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable #-}------------------------------------------------------------------------------------ See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : LookupMap--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke--- License : GPL V2------ Maintainer : Douglas Burke--- Stability : experimental--- Portability : A lot of LANGUAGE extensions...------ This module defines a lookup table format and associated functions--- used by the graph matching code.----------------------------------------------------------------------------------------------------------------------------------------------------- Generic list-of-pairs lookup functions---------------------------------------------------------------module Swish.Utils.LookupMap- ( LookupEntryClass(..), LookupMap(..)- , emptyLookupMap, makeLookupMap, listLookupMap- , reverseLookupMap- , keyOrder- , mapFind, mapFindMaybe, mapContains- , mapReplace, mapReplaceOrAdd, mapReplaceAll, mapReplaceMap- , mapAdd, mapAddIfNew- , mapDelete, mapDeleteAll- , mapApplyToAll, mapTranslate- , mapEq, mapKeys, mapVals- , mapSelect, mapMerge- , mapTranslateKeys, mapTranslateVals- , mapTranslateEntries, mapTranslateEntriesM-- )- where--import qualified Data.Foldable as F-import qualified Data.Traversable as T-import qualified Data.List as L--import Control.Arrow (first, second)--import Data.Ord (comparing)--import Swish.Utils.ListHelpers (equiv)---- this is in Data.Tuple in base 4.3-swap :: (a,b) -> (b,a)-swap (a,b) = (b,a)----------------------------------------------------------------- Class for lookup map entries----------------------------------------------------------------- |@LookupEntryClass@ defines essential functions of any datatype--- that can be used to make a 'LookupMap'.------ Minimal definition: @newEntry@ and @keyVal@----class (Eq k, Show k) => LookupEntryClass a k v | a -> k, a -> v- where- newEntry :: (k,v) -> a- keyVal :: a -> (k,v)- - entryKey :: a -> k- entryKey = fst . keyVal- - entryVal :: a -> v- entryVal = snd . keyVal- - entryEq :: (Eq v) => a -> a -> Bool- entryEq e1 e2 = keyVal e1 == keyVal e2- - entryShow :: (Show v) => a -> String- entryShow e = show k ++ ":" ++ show v where (k,v) = keyVal e- - kmap :: (LookupEntryClass a2 k2 v) => (k -> k2) -> a -> a2- kmap f = newEntry . first f . keyVal- - vmap :: (LookupEntryClass a2 k v2) => (v -> v2) -> a -> a2- vmap f = newEntry . second f . keyVal---- |Predefine a pair of appropriate values as a valid lookup table entry--- (i.e. an instance of LookupEntryClass).----instance (Eq k, Show k) => LookupEntryClass (k,v) k v where- newEntry = id- keyVal = id---- |Define a lookup map based on a list of values.------ Note: the class constraint that a is an instance of 'LookupEntryClass'--- is not defined here, for good reasons (which I forget right now, but--- something to do with the method dictionary being superfluous on--- an algebraic data type).----data LookupMap a = LookupMap [a]- deriving (Functor, F.Foldable, T.Traversable)--{- -TODO: could add--instance Monoid (LookupMap a) where- mempty = LookupMap []- mappend = mapMerge--but may need constraints on a, do not-want to add instances at this time, and is-it really useful? ---}--gLM :: LookupMap a -> [a]-gLM (LookupMap es) = es---- TODO: See also 'mapEq'--- (why not just use that for the Eq instance? I don't know: it's probably historic.)------- |Define equality of 'LookupMap' values based on equality of entries.------ (This is possibly a poor definition, as it is dependent on ordering--- of list members. But it passes all current test cases, and is used--- only for testing.)----instance (Eq a) => Eq (LookupMap a) where- LookupMap es1 == LookupMap es2 = es1 == es2---- |Define Show instance for LookupMap based on Showing the--- list of entries.----instance (Show a ) => Show (LookupMap a) where- show (LookupMap es) = "LookupMap " ++ show es--{--TODO: should the LookupEntryClass constraint be removed from-emptyLookupMap and makeLookupMap?--I guess not since LookupMap is exported, so users can use-that if they do not need the constraint.--}---- |Empty lookup map of arbitrary (i.e. polymorphic) type.----emptyLookupMap :: (LookupEntryClass a k v) => LookupMap a-emptyLookupMap = LookupMap []---- |Function to create a `LookupMap` from a list of entries.------ Currently, this is trivial but future versions could be--- more substantial.----makeLookupMap :: (LookupEntryClass a k v) => [a] -> LookupMap a-makeLookupMap = LookupMap---- |Return list of lookup map entries.------ Currently, this is trivial but future versions could be--- more substantial.----listLookupMap :: (LookupEntryClass a k v) => LookupMap a -> [a]-listLookupMap = gLM---- |Given a lookup map entry, return a new entry that can be used--- in the reverse direction of lookup. This is used to construct--- a reverse LookupMap.----reverseEntry :: (LookupEntryClass a1 k v, LookupEntryClass a2 v k)- => a1 -> a2-reverseEntry = newEntry . swap . keyVal---- |Given a lookup map, return a new map that can be used--- in the opposite direction of lookup.----reverseLookupMap :: (LookupEntryClass a1 b c, LookupEntryClass a2 c b)- => LookupMap a1 -> LookupMap a2-reverseLookupMap = fmap reverseEntry---- |Given a pair of lookup entry values, return the ordering of their--- key values.----keyOrder :: (LookupEntryClass a k v, Ord k)- => a -> a -> Ordering-keyOrder = comparing entryKey---- Local helper function to build a new LookupMap from--- a new entry and an exiting map.----mapCons :: a -> LookupMap a -> LookupMap a-mapCons e (LookupMap es) = LookupMap (e:es)---- |Find key in lookup map and return corresponding value,--- otherwise return default supplied.----mapFind :: (LookupEntryClass a k v) => v -> k -> LookupMap a -> v-mapFind def key (LookupMap es) = foldr match def es where- match ent alt- | key == entryKey ent = entryVal ent- | otherwise = alt---- |Find key in lookup map and return Just the corresponding value,--- otherwise return Nothing.----mapFindMaybe :: (LookupEntryClass a k v) => k -> LookupMap a -> Maybe v-mapFindMaybe key (LookupMap es) = foldr match Nothing es where- match ent alt- | key == entryKey ent = Just (entryVal ent)- | otherwise = alt---- |Test to see if key is present in the supplied map----mapContains :: (LookupEntryClass a k v) =>- LookupMap a -> k -> Bool-mapContains (LookupMap es) key = any match es where- match ent = key == entryKey ent---- |Replace an existing occurrence of a key a with a new key-value pair.--- --- The resulting lookup map has the same form as the original in all--- other respects. Assumes exactly one occurrence of the supplied key.----mapReplace :: (LookupEntryClass a k v) =>- LookupMap a -> a -> LookupMap a-mapReplace (LookupMap (e:es)) newe- | entryKey e == entryKey newe = LookupMap (newe:es)- | otherwise = mapAdd more e where- more = mapReplace (LookupMap es) newe-mapReplace _ newe =- error ("mapReplace: Key value not found in lookup table: "++- Prelude.show (entryKey newe))---- |Replace an existing occurrence of a key a with a new key-value pair,--- or add a new key-value pair if the supplied key is not already present.----mapReplaceOrAdd :: (LookupEntryClass a k v) =>- a -> LookupMap a -> LookupMap a-mapReplaceOrAdd newe (LookupMap (e:es))- | entryKey e == entryKey newe = LookupMap (newe:es)- | otherwise = mapCons e more where- more = mapReplaceOrAdd newe (LookupMap es)-mapReplaceOrAdd newe (LookupMap []) = LookupMap [newe]---- |Replace any occurrence of a key a with a new key-value pair.------ The resulting lookup map has the same form as the original in all--- other respects.----mapReplaceAll :: (LookupEntryClass a k v) =>- LookupMap a -> a -> LookupMap a-mapReplaceAll (LookupMap (e:es)) newe = mapCons e' more where- more = mapReplaceAll (LookupMap es) newe- e' = if entryKey e == entryKey newe then newe else e-mapReplaceAll (LookupMap []) _ = LookupMap []---- |Replace any occurrence of a key in the first argument with a--- corresponding key-value pair from the second argument, if present.------ This could be implemented by multiple applications of 'mapReplaceAll',--- but is arranged differently so that only one new @LookupMap@ value is--- created.------ Note: keys in the new map that are not present in the old map--- are not included in the result map----mapReplaceMap :: (LookupEntryClass a k v) =>- LookupMap a -> LookupMap a -> LookupMap a-mapReplaceMap (LookupMap (e:es)) newmap = mapCons e' more where- more = mapReplaceMap (LookupMap es) newmap- e' = newEntry (k, mapFind v k newmap)- (k,v) = keyVal e-mapReplaceMap (LookupMap []) _ = LookupMap []---- |Add supplied key-value pair to the lookup map.------ This is effectively an optimized case of 'mapReplaceOrAdd' or 'mapAddIfNew',--- where the caller guarantees to avoid duplicate key values.----mapAdd :: LookupMap a -> a -> LookupMap a-mapAdd emap e = mapCons e emap---- |Add supplied key-value pair to the lookup map,--- only if the key value is not already present.----mapAddIfNew :: (LookupEntryClass a k v) =>- LookupMap a -> a -> LookupMap a-mapAddIfNew emap e = if mapContains emap (entryKey e)- then emap- else mapCons e emap---- |Delete supplied key value from the lookup map.------ This function assumes exactly one occurrence.----mapDelete :: (LookupEntryClass a k v) =>- LookupMap a -> k -> LookupMap a-mapDelete (LookupMap (e:es)) k- | k == entryKey e = LookupMap es- | otherwise = mapCons e more where- more = mapDelete (LookupMap es) k-mapDelete _ k =- error ("mapDelete: Key value not found in lookup table: " ++ Prelude.show k)---- |Delete any occurrence of a supplied key value from the lookup map.----mapDeleteAll :: (LookupEntryClass a k v) =>- LookupMap a -> k -> LookupMap a-mapDeleteAll (LookupMap (e:es)) k =- if entryKey e == k then more else mapCons e more where- more = mapDeleteAll (LookupMap es) k-mapDeleteAll (LookupMap []) _ = LookupMap []---- |Return a list of values obtained by applying a function to each key--- in the map. Creates an alternative set of values that can be--- retrieved using mapTranslate.----mapApplyToAll :: (LookupEntryClass a k v) =>- LookupMap a -> (k -> w) -> [w]-mapApplyToAll es f = gLM $ fmap (f . entryKey) es---- |Find a node in a lookup map list, and returns the--- corresponding value from a supplied list. The appropriate ordering--- of the list is not specified here, but an appropriately ordered list--- may be obtained by 'mapApplyToAll'.----mapTranslate :: (LookupEntryClass a k v) =>- LookupMap a -> [w] -> k -> w -> w-mapTranslate (LookupMap (e:es)) (w:ws) k def- | k == entryKey e = w- | otherwise = mapTranslate (LookupMap es) ws k def-mapTranslate _ _ _ def = def---- |Compare two lookup maps for equality.------ Two maps are equal if they have the same set of keys, and if--- each key maps to an equivalent value.----mapEq :: (LookupEntryClass a k v, Eq v) =>- LookupMap a -> LookupMap a -> Bool-mapEq es1 es2 =- ks1 `equiv` ks2 &&- and [ mapFindMaybe k es1 == mapFindMaybe k es2 | k <- ks1 ]- where- ks1 = mapKeys es1- ks2 = mapKeys es2---- |Return the list of keys in a supplied LookupMap----mapKeys :: (LookupEntryClass a k v) =>- LookupMap a -> [k]-mapKeys (LookupMap es) = L.nub $ map entryKey es---- |Return list of distinct values in a supplied LookupMap----mapVals :: (Eq v, LookupEntryClass a k v) =>- LookupMap a -> [v]-mapVals (LookupMap es) = L.nub $ map entryVal es---- |Select portion of a lookup map that corresponds to--- a supplied list of keys----mapSelect :: (LookupEntryClass a k v) =>- LookupMap a -> [k] -> LookupMap a-mapSelect (LookupMap es) ks =- LookupMap $ filter (keyIn ks) es- where- keyIn iks e = entryKey e `elem` iks---- |Merge two lookup maps, ensuring that if the same key appears--- in both maps it is associated with the same value.----mapMerge :: (LookupEntryClass a k v, Eq a, Show a, Ord k) =>- LookupMap a -> LookupMap a -> LookupMap a-mapMerge (LookupMap s1) (LookupMap s2) =- LookupMap $ merge (L.sortBy keyOrder s1) (L.sortBy keyOrder s2)- where- merge es1 [] = es1- merge [] es2 = es2- merge es1@(e1:et1) es2@(e2:et2) =- case keyOrder e1 e2 of- LT -> e1 : merge et1 es2- GT -> e2 : merge es1 et2- EQ -> if e1 /= e2- then error ("mapMerge key conflict: " ++ show e1- ++ " with " ++ show e2)- else e1 : merge et1 et2---- |An fmap-like function that returns a new lookup map that is a--- copy of the supplied map with entry keys replaced according to--- a supplied function.----mapTranslateKeys :: (LookupEntryClass a1 k1 v, LookupEntryClass a2 k2 v) =>- (k1 -> k2) -> LookupMap a1 -> LookupMap a2-mapTranslateKeys f = fmap (kmap f)---- |An fmap-like function that returns a new lookup map that is a--- copy of the supplied map with entry values replaced according to--- a supplied function.----mapTranslateVals :: (LookupEntryClass a1 k v1, LookupEntryClass a2 k v2) =>- (v1 -> v2) -> LookupMap a1 -> LookupMap a2-mapTranslateVals f = fmap (vmap f)---- |A function that returns a new lookup map that is a copy of the--- supplied map with complete entries replaced according to--- a supplied function.----mapTranslateEntries :: (a1 -> a2) -> LookupMap a1 -> LookupMap a2-mapTranslateEntries = fmap---- |A monadic form of `mapTranslateEntries` which is--- the same as `Data.Traversable.mapM`.------ Since `LookupMap` now has a `Data.Traversable.Traversable` instance--- this is just `T.mapM`.----mapTranslateEntriesM :: (Monad m)- => (a1 -> m a2) -> LookupMap a1 -> m (LookupMap a2)-mapTranslateEntriesM = T.mapM -{--mapTranslateEntriesM f (LookupMap es) =- do { m2 <- mapM f es- ; return $ LookupMap m2- }--}---------------------------------------------------------------------------------------- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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/Utils/MiscHelpers.hs
@@ -1,69 +0,0 @@------------------------------------------------------------------------------------ See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : MiscHelpers--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke--- License : GPL V2------ Maintainer : Douglas Burke--- Stability : experimental--- Portability : H98------ This module defines a random set of helper functions--- used by the graph handling code.------ This module is *deprecated* and will be removed at the next possible--- release.--------------------------------------------------------------------------------------module Swish.Utils.MiscHelpers- ( hash- , hashModulus- )-where----------------------------------------------------------------- Hash function and values------------------------------------------------------------------- Simple hash function based on Sedgewick, Algorithms in C, p 233--- (choose mx*cm+255 < maxBound)--- 'seed' is an additional parameter that allows the function--- to be varied for re-hashing.--hashModulus :: Int-hashModulus = 16000001--hash :: Int -> String -> Int-hash seed = hash1 seed (64+seed) hashModulus --hash1 :: Int -> Int -> Int -> String -> Int-hash1 sofar cm mx (c:str) = hash1 (( sofar*cm + fromEnum c ) `rem` mx) cm mx str-hash1 sofar _ _ [] = sofar----------------------------------------------------------------------------------------- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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/Utils/Namespace.hs
@@ -1,222 +0,0 @@-{-# LANGUAGE TypeSynonymInstances, MultiParamTypeClasses, FlexibleInstances #-}-{-# LANGUAGE OverloadedStrings #-}------------------------------------------------------------------------------------- See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : Namespace--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke--- License : GPL V2------ Maintainer : Douglas Burke--- Stability : experimental--- Portability : TypeSynonymInstances, MultiParamTypeClasses, FlexibleInstances, 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.Utils.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.Utils.QName (QName, newQName, getQNameURI, getNamespace, getLocalName)-import Swish.Utils.LookupMap (LookupEntryClass(..))--import Data.Monoid (Monoid(..))-import Data.String (IsString(..))-import Data.Maybe (fromMaybe)--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- -{- - {- nsPrefix :: Maybe T.Text- , nsURI :: URI- }--}- -getNamespacePrefix :: Namespace -> Maybe T.Text-getNamespacePrefix (Namespace p _) = p--getNamespaceURI :: Namespace -> URI-getNamespaceURI (Namespace _ u) = u--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 Show Namespace where- show (Namespace (Just p) u) = show p ++ ":<" ++ show u ++ ">"- show (Namespace _ u) = "<" ++ show u ++ ">"--instance LookupEntryClass Namespace (Maybe T.Text) URI where- keyVal (Namespace pre uri) = (pre,uri)- newEntry (pre,uri) = Namespace pre uri--makeNamespace :: Maybe T.Text -> URI -> Namespace-makeNamespace = Namespace--makeNamespaceQName :: Namespace -> T.Text -> 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 T.Text---- | Returns the local part.-getScopeLocal :: ScopedName -> T.Text-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 QNames are equal-instance Eq ScopedName where- (ScopedName qn1 _ _) == (ScopedName qn2 _ _) = qn1 == qn2---- | Scoped names are ordered by their QNames-instance Ord ScopedName where- (ScopedName qn1 _ _) <= (ScopedName qn2 _ _) = qn1 <= qn2---- | If there is a namespace associated then the Show instance--- uses @prefix:local@, otherwise @<url>@.-instance Show ScopedName where- show (ScopedName qn n l) = case getNamespacePrefix n of- Just pre -> T.unpack $ mconcat [pre, ":", 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---- for the moment leave matchName using String rather than Text---- |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 from prefix, URI and local name-makeScopedName :: Maybe T.Text -> URI -> T.Text -> ScopedName-makeScopedName pre nsuri local =- ScopedName (newQName nsuri local) (Namespace pre nsuri) local---- |Construct a ScopedName from a QName.-makeQNameScopedName :: Maybe T.Text -> QName -> ScopedName-makeQNameScopedName pre qn = ScopedName qn (Namespace pre (getNamespace qn)) (getLocalName qn)---- | Construct a ScopedName for a bare URI (the label is set to \"\").-makeURIScopedName :: URI -> ScopedName-makeURIScopedName uri = makeScopedName Nothing uri ""---- | Construct a ScopedName from a Namespace and local component-makeNSScopedName :: Namespace -> T.Text -> 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 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/Utils/PartOrderedCollection.hs
@@ -1,386 +0,0 @@------------------------------------------------------------------------------------ See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : PartOrderedCollection--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 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 Swish.Utils.PartOrderedCollection- ( PartCompare- , minima- , maxima- , partCompareEq- , partComparePair- , partCompareListMaybe- , partCompareListSubset- , partCompareListPartOrd- , partCompareMaybe- - -- the following are currently unused- , partCompareOrd- , partCompareListOrd - )- where--import Data.List (foldl')----------------------------------------------------------------- Type of partial compare 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 as = foldl' add [] as- 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) => a -> a -> Maybe Ordering-partCompareEq a1 a2 = if a1 == a2 then Just EQ else Nothing---- |Partial ordering for Ord values-partCompareOrd :: (Ord a) => a -> a -> Maybe Ordering-partCompareOrd a1 a2 = Just $ compare a1 a2---- |Part-ordering comparison on pairs of values,--- where each has a part-ordering relationship-partComparePair ::- (a->a->Maybe Ordering) -> (b->b->Maybe Ordering) -> (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 on lists of Ord 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----partCompareListOrd :: (Ord a) => [a] -> [a] -> Maybe Ordering-partCompareListOrd = partCompareListPartOrd (Just `c2` compare)- where c2 = (.) . (.)---- |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 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/Utils/QName.hs
@@ -1,277 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}------------------------------------------------------------------------------------- See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : QName--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke--- License : GPL V2------ Maintainer : Douglas Burke--- Stability : experimental--- Portability : OverloadedStrings------ This module defines an algebraic datatype for qualified names (QNames).---------------------------------------------------------------------------------------- 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.Utils.QName- ( QName- , newQName- , qnameFromURI- , getNamespace- , getLocalName- , getQNameURI- , qnameFromFilePath- )- where--import System.Directory (canonicalizePath)--import Network.URI (URI(..), URIAuth(..)- , parseURIReference)--import Data.String (IsString(..))-import Data.Maybe (fromMaybe)--import Data.Interned (intern, unintern)-import Data.Interned.URI (InternedURI)--import qualified Data.Text as T----------------------------------------------------------------- Qualified name------------------------------------------------------------------- cf. http://www.w3.org/TR/REC-xml-names/--{-| --A qualified name, consisting of a namespace URI-and the local part of the identifier.---}--{--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 T.Text---- | This is not total since it will fail if the input string is not a valid URI.-instance IsString QName where- fromString = qnameFromString- --- | Equality is determined by a case sensitive comparison of the --- URI.-instance Eq QName where- -- see qnEq comments below- (QName u1 _ _) == (QName u2 _ _) = u1 == u2---- ugly, use show instance OR switch to the ordering of InternedURI- -instance Ord QName where- {-- (QName u1 l1) <= (QName u2 l2) =- if up1 /= up2 then up1 <= up2 else (ur1++l1) <= (ur2++l2)- where- n = min (length u1) (length u2)- (up1,ur1) = splitAt n u1- (up2,ur2) = splitAt n u2- -}- - -- TODO: which is faster?- -- Now we have changed to InternedURI, we could use the- -- Ord instance of it, but it is unclear to me what the- -- ordering means in that case, and whether the semantics- -- matter here?- (QName u1 _ _) <= (QName u2 _ _) = show u1 <= show u2- - {-- (QName _ uri1 l1) <= (QName _ uri2 l2) =- if up1 /= up2 then up1 <= up2 else (ur1 ++ T.unpack l1) <= (ur2 ++ T.unpack l2)- where- u1 = show uri1- u2 = show uri2- - n = min (length u1) (length u2)- (up1,ur1) = splitAt n u1- (up2,ur2) = splitAt n u2- -}- --- | The format used to display the URI is @<uri>@.-instance Show QName where- show (QName u _ _) = "<" ++ show u ++ ">"--{--Should this be clever and ensure that local doesn't-contain /, say?--We could also me more clever, and safer, when constructing-the overall uri.--}-newQName :: URI -> T.Text -> QName-newQName ns local = - let l = T.unpack local- uristr = show ns ++ l- uri = fromMaybe (error ("Unable to parse URI from: '" ++ show ns ++ "' + '" ++ l ++ "'")) (parseURIReference uristr)- - {- the following does not work since the semantics of relativeTo do not match the required- behavior here. It may well be better to do something like the following, writing a replacement for- relativeTo, but leave that for a later date.- - let l = T.unpack local- luri = fromMaybe (error ("Unable to parse local name as a URI reference: '" ++ l ++ "'")) (parseRelativeReference l)- uri = fromMaybe (error ("Unable to combine " ++ show ns ++ " with " ++ l)) $ luri `relativeTo` ns- -}- - in QName (intern uri) ns local--{---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.---}--qnameFromURI :: URI -> QName-qnameFromURI uri =- let uf = uriFragment uri- up = uriPath uri- q0 = QName iuri uri ""- iuri = intern uri- in case uf of- "#" -> q0- '#':xs -> QName iuri (uri { uriFragment = "#" }) (T.pack xs)- "" -> case break (=='/') (reverse up) of- ("",_) -> q0 -- path ends in / or is empty- (_,"") -> q0 -- path contains no /- (rlname,rpath) -> QName iuri (uri {uriPath = reverse rpath}) (T.pack (reverse rlname))- - e -> error $ "Unexpected: uri=" ++ show uri ++ " has fragment='" ++ show e ++ "'" --qnameFromString :: String -> QName-qnameFromString s = - maybe (error ("Unable to convert '" ++ s ++ "' into a QName"))- qnameFromURI (parseURIReference s)---- | 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 -> T.Text-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--{--Original used comparison of concatenated strings,-but that was very inefficient. The longer version below-does the comparison without constructing new values but is-no longer valid with the namespace being stored as a URI,-so for now just compare the overall URIs and we can-optimize this at a later date if needed.-qnEq :: QName -> QName -> Bool-qnEq (QName u1 _ _) (QName u2 _ _) = u1 == u2--qnEq (QName _ n1 l1) (QName _ n2 l2) = qnEq1 n1 n2 l1 l2- where- qnEq1 (c1:ns1) (c2:ns2) ln1 ln2 = c1==c2 && qnEq1 ns1 ns2 ln1 ln2- qnEq1 [] ns2 ln1@(_:_) ln2 = qnEq1 ln1 ns2 [] ln2- qnEq1 ns1 [] ln1 ln2@(_:_) = qnEq1 ns1 ln2 ln1 []- qnEq1 [] [] [] [] = True- qnEq1 _ _ _ _ = False--}--{-|-Convert a filepath to a file: URI stored in a QName. If the-input file path is relative then the 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 what 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"---}---- since we build up the URI manually we could--- create the QName directly, but leave that --- for now.--qnameFromFilePath :: FilePath -> IO QName-qnameFromFilePath = fmap qnameFromURI . filePathToURI- -emptyAuth :: Maybe URIAuth-emptyAuth = Just $ URIAuth "" "" ""--filePathToURI :: FilePath -> IO URI-filePathToURI fname = do- ipath <- canonicalizePath fname- - {-- let paths = splitDirectories ipath- txt = intercalate "/" $ case paths of- "/":rs -> rs- _ -> paths- -}- - -- Is manually creating the URI sensible?- -- return $ fromJust $ parseURI $ "file:///" ++ txt- -- return $ URI "file:" emptyAuth txt "" ""- return $ URI "file:" emptyAuth ipath "" ""---------------------------------------------------------------------------------------- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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/Utils/ShowM.hs
@@ -1,75 +0,0 @@------------------------------------------------------------------------------------ See end of this file for licence information.------------------------------------------------------------------------------------ |--- Module : ShowM--- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 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 ShowM interface, and------ (3) uses a ShowS intermediate value to avoid unnecessary--- concatenation of long strings.--------------------------------------------------------------------------------------module Swish.Utils.ShowM (ShowM(..)) where----------------------------------------------------------------- ShowM framework----------------------------------------------------------------- |ShowM is a type class for values that may be formatted in--- multi-line displays.-class (Show sh) => ShowM 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.- showms :: String -> sh -> ShowS- showms _ = shows---------------------------------------------------------------------------------------- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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------------------------------------------------------------------------------------
+ app/SwishApp.hs view
@@ -0,0 +1,90 @@+--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : SwishApp+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : H98+--+-- This module is a wrapper for the main program of Swish.+--+--------------------------------------------------------------------------------++module Main where++import Paths_swish (version)+import Data.Version (showVersion)++import System.Environment (getArgs)+import System.Exit (ExitCode(ExitSuccess,ExitFailure), exitWith)++import Control.Monad (unless)++import System.IO (stderr, hPutStrLn)++import Swish++------------------------------------------------------------+-- Swish main program+------------------------------------------------------------+--+-- This is a minimal wrapper for the real main program, to facilitate+-- interactive execution (e.g. in HUGS) of different command lines.+--+-- execStateT runs the monad with a supplied initial state,+-- then separates the resulting state from the IO monad.++main :: IO ()+main = do+ args <- getArgs+ let (flags, cmds) = splitArguments args+ doHelp = "-h" `elem` flags+ doVersion = "-v" `elem` flags+ doQuiet = "-q" `elem` flags+ + if doHelp || doVersion+ then if doHelp then displaySwishHelp else displayVersion >> exitWith ExitSuccess+ else do+ unless doQuiet $ displayVersion >> putStrLn "\n"+ case validateCommands cmds of+ Left (emsg, ecode) -> do+ hPutStrLn stderr $ "Swish: " ++ emsg+ exitWith $ ExitFailure $ fromEnum ecode+ + Right acts -> do+ code <- runSwishActions acts+ case code of+ SwishSuccess -> exitWith ExitSuccess+ _ -> hPutStrLn stderr ("Swish: "++show code)+ >> exitWith (ExitFailure $ fromEnum code)+ +displayVersion :: IO ()+displayVersion = putStrLn $ "Swish " ++ showVersion version + +--------------------------------------------------------------------------------+--+-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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+--+--------------------------------------------------------------------------------
+ src/Data/Interned/URI.hs view
@@ -0,0 +1,91 @@+{-# LANGUAGE TypeFamilies, FlexibleInstances #-}++--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : URI+-- Copyright : (c) 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : TypeFamilies, FlexibleInstances+--+-- 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 each component+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 (Eq) -- DU {-# UNPACK #-} !URI deriving (Eq) + describe = DU+ identify = InternedURI+ identity (InternedURI i _) = i+ 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+ hash = hashWithSalt 5381 -- use the stringSalt value from Data.Hashable+ 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 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+--+--------------------------------------------------------------------------------
src/Swish.hs view
@@ -10,64 +10,53 @@ -- Stability : experimental -- Portability : H98 ----- This module is a wrapper for the main program of Swish.+-- Swish: Semantic Web Inference Scripting in Haskell -------------------------------------------------------------------------------------module Main where--import Paths_swish (version)-import Data.Version (showVersion)--import System.Environment (getArgs)-import System.Exit (ExitCode(ExitSuccess,ExitFailure), exitWith)--import Control.Monad (unless)--import System.IO (stderr, hPutStrLn)--import Swish----------------------------------------------------------------- Swish main program-------------------------------------------------------------+-- 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). ----- This is a minimal wrapper for the real main program, to facilitate--- interactive execution (e.g. in HUGS) of different command lines.+-- 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]. ----- execStateT runs the monad with a supplied initial state,--- then separates the resulting state from the IO monad.+-- The script format used by Swish is described in+-- "Swish.RDF.SwishScript".+--+-- Users wishing to process RDF data directly may prefer to look at+-- the following modules; "Swish.RDF", "Swish.RDF.TurtleParser",+-- "Swish.RDF.N3Parser", "Swish.RDF.N3Formatter", "Swish.RDF.NTParser"+-- and "Swish.RDF.NTFormatter".+--+-- (1) Semantic web: <http://www.w3.org/2001/sw/>+--+-- (2) CWM: <http://www.w3.org/2000/10/swap/doc/cwm.html>+--+-- (3) Motivation: <http://www.w3.org/2000/10/swap/doc/Motivation.html>+--+-- (4) Notation 3: <http://www.w3.org/TeamSubmission/2008/SUBM-n3-20080114/>+--+-- (5) Turtle: <http://www.w3.org/TR/turtle/>+--+-- (6) RDF: <http://www.w3.org/RDF/>+--+-------------------------------------------------------------------------------- -main :: IO ()-main = do- args <- getArgs- let (flags, cmds) = splitArguments args- doHelp = "-h" `elem` flags- doVersion = "-v" `elem` flags- doQuiet = "-q" `elem` flags- - if doHelp || doVersion- then if doHelp then displaySwishHelp else displayVersion >> exitWith ExitSuccess- else do- unless doQuiet $ displayVersion >> putStrLn "\n"- case validateCommands cmds of- Left (emsg, ecode) -> do- hPutStrLn stderr $ "Swish: " ++ emsg- exitWith $ ExitFailure $ fromEnum ecode- - Right acts -> do- code <- runSwishActions acts- case code of- SwishSuccess -> exitWith ExitSuccess- _ -> hPutStrLn stderr ("Swish: "++show code)- >> exitWith (ExitFailure $ fromEnum code)- -displayVersion :: IO ()-displayVersion = putStrLn $ "Swish " ++ showVersion version - +module Swish+ (+ module Swish.RDF.SwishMain+ ) where++import Swish.RDF.SwishMain+-- should this re-export Swish.RDF.SwishMain or other components?+ -------------------------------------------------------------------------------- ----- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke +-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke -- All rights reserved. -- -- This file is part of Swish.
+ src/Swish/RDF.hs view
@@ -0,0 +1,44 @@+--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : RDF+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : FlexibleInstances, MultiParamTypeClasses, TypeSynonymInstances+--+-- This module provides an in-memory RDF Graph (it re-exports+-- "Swish.RDF.RDFGraph").+--+--------------------------------------------------------------------------------++module Swish.RDF (module Swish.RDF.RDFGraph) where++import Swish.RDF.RDFGraph++--------------------------------------------------------------------------------+--+-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/BuiltInDatatypes.hs view
@@ -0,0 +1,74 @@+--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : BuiltInDatatypes+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 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.BuiltInDatatypes+ ( allDatatypes, findRDFDatatype )+ where++import Swish.RDF.RDFDatatype (RDFDatatype)++import Swish.Utils.LookupMap (LookupMap(..), mapFindMaybe)+import Swish.Utils.Namespace (ScopedName)+import Swish.RDF.RDFDatatypeXsdString (rdfDatatypeXsdString)+import Swish.RDF.RDFDatatypeXsdInteger (rdfDatatypeXsdInteger)++------------------------------------------------------------+-- Declare datatype map+------------------------------------------------------------++allDatatypes :: [RDFDatatype]+allDatatypes =+ [ rdfDatatypeXsdString+ , rdfDatatypeXsdInteger+ ]++findRDFDatatype :: ScopedName -> Maybe RDFDatatype+findRDFDatatype nam = mapFindMaybe nam (LookupMap allDatatypes)++------------------------------------------------------------+-- Declare datatype subtypes map+------------------------------------------------------------++{-+allDatatypeSubtypes :: [xxx]+allDatatypeSubtypes = []+-- [[[details TBD]]]+-}++--------------------------------------------------------------------------------+--+-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/BuiltInMap.hs view
@@ -0,0 +1,52 @@+--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : BuiltInMap+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 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.BuiltInMap+ ( findRDFOpenVarBindingModifier+ , findRDFDatatype+ , rdfRulesetMap+ , allRulesets, allDatatypeRulesets+ )+where++import Swish.RDF.BuiltInDatatypes+import Swish.RDF.BuiltInRules++--------------------------------------------------------------------------------+--+-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/BuiltInRules.hs view
@@ -0,0 +1,157 @@+--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : BuiltInRules+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 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.BuiltInRules+ ( findRDFOpenVarBindingModifier+ , rdfRulesetMap+ , allRulesets, allDatatypeRulesets+ )+where++import Swish.RDF.BuiltInDatatypes (allDatatypes)++import Swish.RDF.RDFVarBinding+ ( RDFOpenVarBindingModify+ , rdfVarBindingUriRef, rdfVarBindingBlank+ , rdfVarBindingLiteral+ , rdfVarBindingUntypedLiteral, rdfVarBindingTypedLiteral+ , rdfVarBindingXMLLiteral, rdfVarBindingDatatyped+ , rdfVarBindingMemberProp+ )++import Swish.RDF.RDFRuleset (RDFRuleset, RDFRulesetMap)++import Swish.RDF.RDFProofContext+ ( rulesetRDF+ , rulesetRDFS+ , rulesetRDFD )++import Swish.RDF.VarBinding+ ( nullVarBindingModify+ , makeVarFilterModify+ , varFilterEQ, varFilterNE+ )++import Swish.RDF.Datatype (typeRules, typeMkModifiers)+import Swish.Utils.LookupMap (LookupMap(..), mapFindMaybe)+import Swish.Utils.Namespace (ScopedName)++------------------------------------------------------------+-- 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)+-}++findRDFOpenVarBindingModifier :: ScopedName -> Maybe RDFOpenVarBindingModify+findRDFOpenVarBindingModifier nam =+ mapFindMaybe nam (LookupMap allOpenVarBindingModify)++------------------------------------------------------------+-- Lookup map for built-in rulesets+------------------------------------------------------------++rdfRulesetMap :: RDFRulesetMap+rdfRulesetMap = LookupMap allRulesets++allRulesets :: [RDFRuleset]+allRulesets =+ [ rulesetRDF+ , rulesetRDFS+ , rulesetRDFD+ ]+ ++ allDatatypeRulesets++allDatatypeRulesets :: [RDFRuleset]+allDatatypeRulesets = map typeRules allDatatypes++--------------------------------------------------------------------------------+--+-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/ClassRestrictionRule.hs view
@@ -0,0 +1,546 @@+{-# LANGUAGE MultiParamTypeClasses, OverloadedStrings #-}++--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : ClassRestrictionRule+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : MultiParamTypeClasses, 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.RDF.RDFGraph+ ( RDFLabel(..)+ , getScopedName+ , RDFGraph+ , getArcs+ , merge+ , toRDFGraph, emptyRDFGraph+ , Arc(..)+ , resRdfType+ , resRdfdMaxCardinality+ )++import Swish.RDF.RDFRuleset (RDFRule, makeRDFGraphFromN3Builder)+import Swish.RDF.RDFDatatype (RDFDatatypeVal, fromRDFLabel, toRDFLabel)++import Swish.RDF.RDFQuery+ ( rdfQueryFind+ , rdfFindValSubj, rdfFindPredVal, rdfFindPredInt+ , rdfFindList+ )++import Swish.RDF.RDFVarBinding (RDFVarBinding)+import Swish.RDF.Datatype (DatatypeVal(..), DatatypeRel(..), DatatypeRelFn)+import Swish.RDF.Rule ( Rule(..), bwdCheckInference)+import Swish.RDF.VarBinding (VarBinding(..))+import Swish.RDF.Vocabulary (namespaceRDFD)++import Swish.Utils.Namespace (Namespace, ScopedName, namespaceToBuilder)+import Swish.Utils.PartOrderedCollection+ ( minima, maxima+ , partCompareEq, partComparePair+ , partCompareListMaybe+ , partCompareListSubset+ )++import Swish.Utils.LookupMap (LookupEntryClass(..), LookupMap(..),mapFindMaybe)+import Swish.Utils.ListHelpers (powerSet)++import Control.Monad (liftM)++import Data.Monoid (Monoid (..))+import Data.Maybe (fromJust, fromMaybe, mapMaybe)+import Data.List (delete, nub, (\\))++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+ }++instance Eq ClassRestriction where+ cr1 == cr2 = crName cr1 == crName cr2++instance Show ClassRestriction where+ show cr = "ClassRestriction:" ++ show (crName cr)++instance LookupEntryClass ClassRestriction ScopedName ClassRestriction+ where+ newEntry (_,fn) = fn+ keyVal cr = (crName cr, 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 =+ liftM (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 . "+ ]+ +-- Placeholder false graph for now.+falseGraph :: RDFGraph+falseGraph = makeRDFGraphFromN3Builder $+ mkPrefix namespaceRDFD `mappend` falseGraphStr++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+ rn = mapFindMaybe (getScopedName r) (LookupMap 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 = nub [Arc ci p v | vs <- nts, (p,v) <- zip props vs ]+ \\ getArcs antgr+ newgrs = if 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 = liftM (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+ makeGraphs = map (toRDFGraph . (:[])) . (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 $ powerSet 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 supplied datatype and graph+------------------------------------------------------------++makeRDFDatatypeRestrictionRules :: RDFDatatypeVal vt -> RDFGraph -> [RDFRule]+makeRDFDatatypeRestrictionRules dtval gr =+ makeRDFClassRestrictionRules dcrs gr+ where+ dcrs = map (makeDatatypeRestriction dtval) (tvalRel dtval)++--------------------------------------------------------------------------------+--+-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/Datatype.hs view
@@ -0,0 +1,1064 @@+{-# LANGUAGE ExistentialQuantification, MultiParamTypeClasses, OverloadedStrings #-}++--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : Datatype+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : ExistentialQuantification, MultiParamTypeClasses, OverloadedStrings+--+-- This module defines the structures used by Swish 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.RDF.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.RDF.Ruleset+ ( Ruleset(..)+ , getRulesetAxiom, getRulesetRule+ )++import Swish.RDF.Rule+ ( Formula(..)+ , Rule(..)+ )++import Swish.RDF.Vocabulary (swishName)+import Swish.Utils.Namespace (ScopedName)++import Swish.RDF.VarBinding+ ( VarBinding(..)+ , addVarBinding+ , VarBindingModify(..), OpenVarBindingModify, nullVarBindingModify+ )++import Swish.Utils.LookupMap+ ( LookupEntryClass(..), LookupMap(..)+ , mapFindMaybe+ )++import Swish.Utils.ListHelpers+ ( flist+ , deleteIndex+ )++-- used to add Show instances for structures during debugging+-- but backed out again.+--+-- import Swish.Utils.ShowM (ShowM(..))++import Data.Maybe( isJust, catMaybes )++import Control.Monad( join, liftM )++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)++instance LookupEntryClass+ (Datatype ex lb vn) ScopedName (Datatype ex lb vn)+ where+ newEntry (_,dt) = dt+ keyVal dt = (typeName dt, dt)++-- |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++getDTRel ::+ ScopedName -> DatatypeVal ex vt lb vn -> Maybe (DatatypeRel vt)+getDTRel nam dtv =+ mapFindMaybe nam (LookupMap (tvalRel dtv))++getDTMod ::+ ScopedName -> DatatypeVal ex vt lb vn -> Maybe (DatatypeMod vt lb vn)+getDTMod nam dtv =+ mapFindMaybe nam (LookupMap (tvalMod dtv))++-- |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 = join $ liftM (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+ }++instance LookupEntryClass (DatatypeRel vt) ScopedName (DatatypeRel vt)+ where+ newEntry (_,relf) = relf+ keyVal dtrel = (dtRelName dtrel, dtrel)++-- |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+ }++instance LookupEntryClass+ (DatatypeMod vt lb vn) ScopedName (DatatypeMod vt lb vn)+ where+ newEntry (_,dmod) = dmod+ keyVal dmod = (dmName dmod, dmod)++-- |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 :: (Eq lb, Show lb, Eq vn, Show 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 :: (Eq lb, Show lb, Eq vn, Show 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 :: (Eq lb, Show lb, Eq vn, Show 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 :: (Eq lb, Show lb, Eq vn, Show 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 :: (Eq lb, Show lb, Eq vn, Show 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 :: (Eq lb, Show lb, Eq vn, Show 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 :: (Eq lb, Show lb, Eq vt, Show 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 :: (Eq lb, Show lb, Eq vt, Show 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 = liftM 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 =+ liftM 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++--------------------------------------------------------+-- 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 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/GraphClass.hs view
@@ -0,0 +1,248 @@+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable #-}++--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : GraphClass+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : DeriveFunctor, DeriveFoldable, DeriveTraversable, 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.RDF.GraphClass+ ( LDGraph(..), replaceArcs+ , Label(..)+ , Arc(..), arcSubj, arcPred, arcObj, arc, arcToTriple, arcFromTriple+ , Selector+ , hasLabel, arcLabels -- , arcNodes+ )+where++import qualified Data.Foldable as F+import qualified Data.Traversable as T++import Data.Hashable (Hashable(..))++import Data.List (foldl', union, (\\))++-- 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: `setArcs`, `getArcs` and `containedIn`+(although @containedIn@ may be removed as it is currently unused).+-}+class (Eq (lg lb), Eq lb ) => LDGraph lg lb+ where+ -- empty graph+ -- emptyGr :: lg lb [[[TODO?]]]+ -- component-level operations+ + -- | Replace the existing arcs in the graph.+ setArcs :: [Arc lb] -> lg lb -> lg lb+ + -- | Extract all the arcs from a graph+ getArcs :: lg lb -> [Arc lb]+ + -- | Extract those arcs that match the given `Selector`.+ extract :: Selector lb -> lg lb -> lg lb+ extract sel = update (filter sel)+ + -- | Add the two graphs+ add :: lg lb -> lg lb -> lg lb+ add addg = update (union (getArcs addg))+ + -- | Remove those arcs in the first graph from the second+ -- graph+ delete :: lg lb -- ^ g1+ -> lg lb -- ^ g2+ -> lg lb -- ^ g2 - g1 -> g3+ delete delg = update (\\ getArcs delg)+ + -- | 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 :: lg lb -> [lb]+ labels g = foldl' union [] (map arcLabels (getArcs g))+ + -- | Enumerate the distinct nodes contained in a graph;+ -- that is, any label that appears in the subject+ -- or object position of an `Arc`.+ nodes :: lg lb -> [lb]+ nodes g = foldl' union [] (map arcNodes (getArcs g))+ + -- | Test for graph containment in another.+ --+ -- At present this is unused and may be removed in a future release.+ containedIn :: lg lb -> lg lb -> Bool + + -- | Update the arcs in a graph using a supplied function.+ update :: ( [Arc lb] -> [Arc lb] ) -> lg lb -> lg lb+ update f g = setArcs ( f (getArcs g) ) g++{-+TODO:+ add a Monoid instance for LDGraph, so that we can remove the+ NSGraph instance in RDFGraph++ This means adding the emptyGr function to the interface+-}++-- |Function to replace arcs in a graph with a given list of arcs.+--+-- This is identical to @flip setArcs@ and so may be removed.+-- +replaceArcs :: (LDGraph lg lb) => lg lb -> [Arc lb] -> lg lb+replaceArcs = flip setArcs+-- replaceArcs gr as = update (const as) gr++-- | 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.++class (Eq lb, Show lb, Ord 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+ + -- compare :: lb -> lb -> Ordering+ -- compare l1 l2 = compare (show l1) (show l2)++-- | Arc type++data Arc lb = Arc + { asubj :: lb -- ^ The subject of the arc.+ , apred :: lb -- ^ The predicate (property) of the arc.+ , aobj :: lb -- ^ The object of the arc.+ }+ deriving (Eq, Functor, F.Foldable, T.Traversable)++instance (Hashable lb) => Hashable (Arc lb) where+ hash (Arc s p o) = hash s `hashWithSalt` p `hashWithSalt` o+ hashWithSalt salt (Arc s p o) = salt `hashWithSalt` s `hashWithSalt` p `hashWithSalt` o++-- | Return the subject of the arc.+arcSubj :: Arc lb -> lb+arcSubj = asubj++-- | Return the predicate (property) of the arc.+arcPred :: Arc lb -> lb+arcPred = apred++-- | Return the object of the arc.+arcObj :: Arc lb -> lb+arcObj = aobj++-- | 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 (Arc s1 p1 o1) (Arc s2 p2 o2)+ | cs /= EQ = cs+ | cp /= EQ = cp+ | otherwise = co+ where+ cs = compare s1 s2+ cp = compare p1 p2+ co = compare o1 o2++ {- not needed+ (Arc s1 p1 o1) <= (Arc s2 p2 o2)+ | s1 /= s2 = s1 <= s2+ | p1 /= p2 = p1 <= p2+ | otherwise = o1 <= o2+ -}++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++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 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/GraphMatch.hs view
@@ -0,0 +1,622 @@+{-# LANGUAGE FlexibleInstances, TypeSynonymInstances, MultiParamTypeClasses #-}++--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : GraphMatch+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : FlexibleInstances, TypeSynonymInstances, MultiParamTypeClasses+--+-- This module contains graph-matching logic.+--+-- The algorithm used is derived from a paper on RDF graph matching+-- by Jeremy Carroll [1].+--+-- [1] <http://www.hpl.hp.com/techreports/2001/HPL-2001-293.html>+--+--------------------------------------------------------------------------------++module Swish.RDF.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 Control.Exception.Base (assert)+import Control.Arrow (second)++import Data.Ord (comparing)+import Data.List (foldl', nub, sortBy, partition)+import Data.Hashable (combine)+import qualified Data.List as L++import Swish.RDF.GraphClass (Arc(..), Label(..),+ arcLabels,+ hasLabel, arcToTriple)+import Swish.Utils.LookupMap (LookupEntryClass(..), LookupMap(..),+ makeLookupMap, listLookupMap, mapFind, mapReplaceAll,+ mapAddIfNew, mapReplaceMap, mapMerge)+import Swish.Utils.ListHelpers (select, equiv, pairSort, pairGroup, pairUngroup)++--------------------------+-- 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 = (Int,Int)++nullLabelVal :: LabelIndex+nullLabelVal = (0,0)++-----------------------+-- Label mapping types+-----------------------++data (Label lb) => GenLabelEntry lb lv = LabelEntry lb lv++type LabelEntry lb = GenLabelEntry lb LabelIndex++instance (Label lb, Eq lb, Show lb, Eq lv, Show lv)+ => LookupEntryClass (GenLabelEntry lb lv) lb lv where+ keyVal (LabelEntry k v) = (k,v)+ newEntry (k,v) = LabelEntry k v++instance (Label lb, Eq lb, Show lb, Eq lv, Show lv)+ => Show (GenLabelEntry lb lv) where+ show = entryShow++instance (Label lb, Eq lb, Show lb, Eq lv, Show lv)+ => Eq (GenLabelEntry lb lv) where+ (==) = entryEq++-- | Type for label->index lookup table+data (Label lb, Eq lv, Show lv) => GenLabelMap lb lv =+ LabelMap Int (LookupMap (GenLabelEntry lb lv))++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 == gen2 && es1 `equiv` es2+ where+ es1 = listLookupMap lmap1+ es2 = listLookupMap lmap2++emptyMap :: (Label lb) => LabelMap lb+emptyMap = LabelMap 1 $ makeLookupMap []++--------------------------+-- 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++------------------------------------------------------------+-- 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++makeScopedLabel :: (Label lb) => Int -> lb -> ScopedLabel lb+makeScopedLabel = ScopedLabel ++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 `combine` scope -- MH.hash seed $ show 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)) ?++-- | 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.)+ -> [Arc lb] -- ^ the first graph to be compared, as a list of arcs+ -> [Arc lb] -- ^ the second graph to be compared, as a list of arcs+ -> (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 " $ -} map (makeScopedArc 1) gs1+ sgs2 = {- trace "sgs2 " $ -} 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++-- | 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.)+--+-- 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.+--++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.+ -> [Arc lb] + -- ^ (@gs1@ argument)+ -- first of two lists of arcs (triples) to be compared+ -> [Arc 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)+ -> [Arc lb] -> [Arc 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 = listLookupMap 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 = mapFind 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 l1)++-- | 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,Int)] -> 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,Int) -> LabelMap lb+setLabelHash (LabelMap g lmap) (lb,lh) =+ LabelMap g ( mapReplaceAll lmap $ newEntry (lb,(g,lh)) )++-- | 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) => [lb] -> LabelMap lb -> LabelMap lb+assignLabelMap ns lmap = foldl' (flip assignLabelMap1) lmap ns++assignLabelMap1 :: (Label lb) => lb -> LabelMap lb -> LabelMap lb+assignLabelMap1 lab (LabelMap g lvs) = LabelMap g lvs'+ where+ lvs' = mapAddIfNew lvs $ newEntry (lab,(g,initVal lab))++-- Calculate initial value for a node++initVal :: (Label lb) => lb -> Int+initVal = hashVal 0++hashVal :: (Label lb) => Int -> lb -> Int+hashVal seed lab =+ if labelIsVar lab then seed `combine` 23 else labelHash seed lab+ -- if labelIsVar lab then hash seed "???" else labelHash seed lab++equivalenceClasses :: + (Label lb) + => LabelMap lb -- ^ label map+ -> [lb] -- ^ list of nodes to be reclassified+ -> [EquivalenceClass lb]+ -- ^ the equivalence classes of the supplied labels under the+ -- supplied label map+equivalenceClasses lmap ls =+ pairGroup $ 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) + => [Arc lb] + -- ^ (the @gs1@ argument) the first of two lists of arcs (triples) to perform a+ -- basis for reclassifying the labels in the first equivalence+ -- class in each pair of @ecpairs@.+ -> [Arc lb]+ -- ^ (the @gs2@ argument) the second of two lists of arcs (triples) 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' = mapReplaceMap lm $ mapMerge 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) = (mapFind nullLabelVal lab x,lab)++-- | Calculate a new index value for a supplied list of labels based on the+-- supplied label map and adjacency calculations in the supplied graph+--+remapLabels :: + (Label lb) + => [Arc 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' (LookupMap newEntries)+ where+ gen' = gen+1+ newEntries = [ newEntry (l, (gen',newIndex l)) | l <- ls ]+ newIndex l+ | labelIsVar l = mapAdjacent l -- adjacency classifies variable labels+ | otherwise = hashVal gen l -- otherwise rehash (to disentangle collisions)+ -- mapAdjacent l = sum (sigsOver l) `rem` hashModulus+ mapAdjacent l = sum (sigsOver l) `combine` hashModulus -- is this a sensible replacement for `rem` MH.hashModulus + sigsOver l = select (hasLabel l) gs (arcSignatures lmap gs)++-- | Return list of distinct labels used in a graph++graphLabels :: (Label lb) => [Arc lb] -> [lb]+graphLabels = nub . concatMap arcLabels++-- | 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 gs =+ map (sigCalc . arcToTriple) gs+ where+ sigCalc (s,p,o) =+ ( labelVal2 s ++ labelVal2 p * 3 ++ labelVal2 o * 5 )+ `combine` hashModulus+ -- `rem` hashModulus+ + 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 ) `equiv` ( graphMap nodeMap gs2 ) then (same)+--+graphMap :: (Label lb) => LabelMap lb -> [Arc lb] -> [Arc LabelIndex]+graphMap = map . fmap . mapLabelIndex -- graphMapStmt++-- | 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 -> [Arc lb] -> [Arc lb] -> Bool+graphMapEq lmap gs1 gs2 = graphMap lmap gs1 `equiv` graphMap lmap gs2++--------------------------------------------------------------------------------+--+-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/GraphMem.hs view
@@ -0,0 +1,166 @@+{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-}+{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable #-}++--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : GraphMem+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : DeriveFunctor, DeriveFoldable, DeriveTraversable, FlexibleInstances, MultiParamTypeClasses+--+-- This module defines a simple memory-based graph instance.+--+--------------------------------------------------------------------------------++------------------------------------------------------------+-- Simple labelled directed graph value+------------------------------------------------------------++module Swish.RDF.GraphMem+ ( GraphMem(..)+ , setArcs, getArcs, add, delete, extract, labels+ , LabelMem(..)+ , labelIsVar, labelHash+ -- For debug/test:+ , matchGraphMem+ ) where++import Swish.RDF.GraphClass+import Swish.RDF.GraphMatch+-- import Swish.Utils.MiscHelpers (hash)++import Data.Hashable (Hashable(..), combine)+import Data.Ord (comparing)++import qualified Data.Foldable as F+import qualified Data.Traversable as T++-- | Memory-based graph type ++data GraphMem lb = GraphMem { arcs :: [Arc lb] }+ deriving (Functor, F.Foldable, T.Traversable)+ +instance (Label lb) => LDGraph GraphMem lb where+ getArcs = arcs+ setArcs as g = g { arcs=as }+ -- gmap f g = g { arcs = (map $ fmap f) (arcs g) }+ containedIn = error "containedIn for LDGraph GraphMem lb is undefined!" -- TODO: should there be one defined?++instance (Label lb) => Eq (GraphMem lb) where+ (==) = graphEq++instance (Label lb) => Show (GraphMem lb) where+ show = graphShow++graphShow :: (Label lb) => GraphMem lb -> String+graphShow g = "Graph:" ++ foldr ((++) . ("\n " ++) . show) "" (arcs g)++{-+toGraph :: (Label lb) => [Arc lb] -> GraphMem lb+toGraph as = GraphMem { arcs=nub as }+-}++-- | 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+ hash (LF l) = 1 `hashWithSalt` l+ hash (LV l) = 2 `hashWithSalt` l+ hashWithSalt salt (LF l) = salt `combine` 1 `hashWithSalt` l+ hashWithSalt salt (LV l) = salt `combine` 2 `hashWithSalt` l++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 seed lb = hash seed (show lb)+ labelHash = hashWithSalt++instance Eq LabelMem where+ (LF l1) == (LF l2) = l1 == l2+ (LV l1) == (LV l2) = l1 == l2+ _ == _ = False++instance Show LabelMem where+ show (LF l1) = '!' : l1+ show (LV l2) = '?' : l2++instance Ord LabelMem where+ compare = comparing show ++--------------------------------------------------------------------------------+--+-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/GraphPartition.hs view
@@ -0,0 +1,570 @@+--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : GraphPartition+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : H98+--+-- This module contains functions for partitioning a graph into subgraphs+-- that rooted from different subject nodes.+--+--------------------------------------------------------------------------------++module Swish.RDF.GraphPartition+ ( PartitionedGraph(..), getArcs, getPartitions+ , GraphPartition(..), node, toArcs+ , partitionGraph, comparePartitions+ , partitionShowP+ )+where++import Swish.RDF.GraphClass+ ( Label(..)+ , Arc(..), arcSubj, arcObj, + -- , hasLabel, arcLabels+ )++import Data.List (foldl', partition)++import Control.Monad.State+ ( MonadState(..), State, evalState )++import Data.Maybe (isJust, fromJust, mapMaybe)+++------------------------------------------------------------+-- 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 (Eq,Show)++getArcs :: PartitionedGraph lb -> [Arc lb]+getArcs (PartitionedGraph ps) = concatMap toArcs ps++getPartitions :: PartitionedGraph lb -> [GraphPartition lb]+getPartitions (PartitionedGraph ps) = ps++-- QUS: is the list always guaranteed to be non-empty in PartSub?+data GraphPartition lb+ = PartObj lb+ | PartSub lb [(lb,GraphPartition lb)]++node :: GraphPartition lb -> lb+node (PartObj ob) = ob+node (PartSub sb _) = sb++toArcs :: GraphPartition lb -> [Arc lb]+toArcs (PartObj _) = []+toArcs (PartSub sb prs) = concatMap toArcs1 prs+ where+ toArcs1 (pr,ob) = Arc sb pr (node ob) : toArcs ob++instance (Label lb) => Eq (GraphPartition lb) where+ (==) = partitionEq++instance (Label lb) => Show (GraphPartition lb) where+ show = partitionShow++-- Equality is based on total structural equivalence.+-- This is not the same as graph equality.+partitionEq :: (Label lb) => GraphPartition lb -> GraphPartition lb -> Bool+partitionEq (PartObj o1) (PartObj o2) = o1 == o2+partitionEq (PartSub s1 p1) (PartSub s2 p2) = s1 == s2 && p1 == p2+partitionEq _ _ = False++partitionShow :: (Label lb) => GraphPartition lb -> String+partitionShow (PartObj ob) = show ob+partitionShow (PartSub sb []) = "(" ++ show sb ++ ")" -- just to make -Wall happy, is this sensible?+partitionShow (PartSub sb (pr:prs)) =+ "("++ show sb ++ " " ++ showpr pr ++ concatMap ((" ; "++).showpr) prs ++ ")"+ where+ showpr (a,b) = show a ++ " " ++ show b+ +partitionShowP :: (Label lb) => String -> GraphPartition lb -> String+partitionShowP _ (PartObj ob) = show ob+partitionShowP pref (PartSub sb []) = pref ++ "(" ++ show sb ++ ")" -- just to make -Wall happy, is this sensible?+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 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 MakePartitionState lb = ([(lb,[Arc lb])],[(lb,[Arc lb])],[(lb,[Arc lb])])++makePartitions :: (Eq lb) =>+ [(lb,[Arc lb])] -> [(lb,[Arc lb])] -> [(lb,[Arc 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 theinitial state of the+-- monad (see call of evalState above).+--+makePartitions1 :: (Eq lb) =>+ [(lb,[Arc lb])] -> State (MakePartitionState 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) =>+ (lb,[Arc lb]) -> State (MakePartitionState lb) [GraphPartition lb]+makePartitions2 subs =+ do { (part,moresubs) <- makeStatements subs+ ; moreparts <- if not (null moresubs) then+ makePartitions1 moresubs+ else+ return []+ ; return $ part:moreparts+ }++makeStatements :: (Eq lb) =>+ (lb,[Arc lb])+ -> State (MakePartitionState lb) (GraphPartition lb,[(lb,[Arc lb])])+makeStatements (sub,stmts) =+ do { propmore <- mapM makeStatement stmts+ ; let (props,moresubs) = unzip propmore+ ; return (PartSub sub props,concat moresubs)+ }++makeStatement :: (Eq lb) =>+ Arc lb+ -> State (MakePartitionState lb) ((lb,GraphPartition lb),[(lb,[Arc 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 :: State (MakePartitionState lb) [(lb,[Arc 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 -> State (MakePartitionState lb) [(lb,[Arc lb])]+pickIntSubject sub =+ do { (s1,s2,s3) <- get+ ; let varsub = removeBy (\x->(x==).fst) sub s3+ ; if isJust varsub then+ do { let (vs,s3new) = fromJust varsub+ ; put (s1,s2,s3new)+ ; return [vs]+ }+ else+ return []+ }++pickVarSubject :: (Eq lb) =>+ lb -> State (MakePartitionState lb) [(lb,[Arc lb])]+pickVarSubject sub =+ do { (s1,s2,s3) <- get+ ; let varsub = removeBy (\x->(x==).fst) sub s2+ ; if isJust varsub then+ do { let (vs,s2new) = fromJust varsub+ ; put (s1,s2new,s3)+ ; return [vs]+ }+ else+ 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 -> [(lb,GraphPartition lb)] -> [(lb,GraphPartition 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) s1s s2s++comparePartitions4 :: (Label lb) =>+ lb -> lb -> (lb,GraphPartition lb) -> (lb,GraphPartition 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,[a])]+collect = collectBy (==)++collectBy :: (b->b->Bool) -> (a->b) -> [a] -> [(b,[a])]+collectBy cmp sel = map reverseCollection . collectBy1 cmp sel []++collectBy1 :: (b->b->Bool) -> (a->b) -> [(b,[a])] -> [a] -> [(b,[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,[a])] -> [(b,[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,[a]) -> (b,[a])+reverseCollection (k,as) = (k,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 remoived 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 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/MapXsdInteger.hs view
@@ -0,0 +1,69 @@+{-# LANGUAGE OverloadedStrings #-}++--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : MapXsdInteger+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 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.MapXsdInteger (mapXsdInteger) where++import Swish.RDF.Datatype (DatatypeMap(..))++import qualified Data.Text as T+import qualified Data.Text.Read as T++------------------------------------------------------------+-- Implementation of DatatypeMap for xsd:integer+------------------------------------------------------------++-- |mapXsdInteger contains 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 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/N3Formatter.hs view
@@ -0,0 +1,941 @@+{-# LANGUAGE OverloadedStrings #-}++--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : N3Formatter+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : OverloadedStrings+--+-- This Module implements a Notation 3 formatter (see [1], [2] and [3]),+-- for an RDFGraph value.+--+-- REFERENCES:+--+-- (1) <http://www.w3.org/TeamSubmission/2008/SUBM-n3-20080114/>+-- Notation3 (N3): A readable RDF syntax,+-- W3C Team Submission 14 January 2008+--+-- (2) <http://www.w3.org/DesignIssues/Notation3.html>+-- Tim Berners-Lee's design issues series notes and description+--+-- (2) <http://www.w3.org/2000/10/swap/Primer.html>+-- 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.N3Formatter+ ( NodeGenLookupMap+ , formatGraphAsText+ , formatGraphAsLazyText+ , formatGraphAsBuilder+ , formatGraphIndent + , formatGraphDiag+ )+where++import Swish.RDF.RDFGraph (+ RDFGraph, RDFLabel(..),+ NamespaceMap, RevNamespaceMap,+ emptyNamespaceMap,+ FormulaMap, emptyFormulaMap,+ getArcs, labels,+ setNamespaces, getNamespaces,+ getFormulae,+ emptyRDFGraph+ , quote+ , quoteT+ , resRdfFirst, resRdfRest, resRdfNil+ )++import Swish.RDF.Vocabulary (+ isLang, langTag, + rdfType,+ rdfNil,+ owlSameAs, logImplies+ , xsdBoolean, xsdDecimal, xsdInteger, xsdDouble + )++import Swish.RDF.GraphClass (Arc(..))++import Swish.Utils.LookupMap+ ( LookupEntryClass(..)+ , LookupMap, emptyLookupMap, reverseLookupMap+ , listLookupMap+ , mapFind, mapFindMaybe, mapAdd, mapDelete, mapMerge+ )++import Swish.Utils.Namespace (ScopedName, getScopeLocal, getScopeURI)++import Data.Char (isDigit)++import Data.List (foldl', delete, groupBy, partition, sort, intersperse)++import Data.Monoid (Monoid(..))+import Control.Monad (liftM, when)+import Control.Monad.State (State, modify, get, put, runState)++-- 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.Text as T+import qualified Data.Text.Lazy as L+import qualified Data.Text.Lazy.Builder as B++-- temporary conversion+quoteB :: Bool -> String -> B.Builder+quoteB f v = B.fromString $ quote f v++----------------------------------------------------------------------+-- 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).++type SubjTree lb = [(lb,PredTree lb)]+type PredTree lb = [(lb,[lb])]++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)]+ , nodeGenSt :: NodeGenState+ , bNodesCheck :: [RDFLabel] -- these bNodes are not to be converted to '[..]' format+ , traceBuf :: [String]+ }+ +type Formatter a = State N3FormatterState a++emptyN3FS :: NodeGenState -> N3FormatterState+emptyN3FS ngs = N3FS+ { indent = "\n"+ , lineBreak = False+ , graph = emptyRDFGraph+ , subjs = []+ , props = []+ , objs = []+ , formAvail = emptyFormulaMap+ , formQueue = []+ , nodeGenSt = ngs+ , bNodesCheck = []+ , traceBuf = []+ }++-- | Node name generation state information that carries through+-- and is updated by nested formulae+type NodeGenLookupMap = LookupMap (RDFLabel,Int)++data NodeGenState = Ngs+ { prefixes :: NamespaceMap+ , nodeMap :: NodeGenLookupMap+ , nodeGen :: Int+ }++emptyNgs :: NodeGenState+emptyNgs = Ngs+ { prefixes = emptyLookupMap+ , nodeMap = emptyLookupMap+ , nodeGen = 0+ }++-- simple context for label creation+-- (may be a temporary solution to the problem+-- of label creation)+--+data LabelContext = SubjContext | PredContext | ObjContext+ deriving (Eq, Show)++getIndent :: Formatter B.Builder+getIndent = indent `liftM` get++setIndent :: B.Builder -> Formatter ()+setIndent ind = modify $ \st -> st { indent = ind }++getLineBreak :: Formatter Bool+getLineBreak = lineBreak `liftM` get++setLineBreak :: Bool -> Formatter ()+setLineBreak brk = modify $ \st -> st { lineBreak = brk }++getNgs :: Formatter NodeGenState+getNgs = nodeGenSt `liftM` get++setNgs :: NodeGenState -> Formatter ()+setNgs ngs = modify $ \st -> st { nodeGenSt = ngs }++getPrefixes :: Formatter NamespaceMap+getPrefixes = prefixes `liftM` getNgs++getSubjs :: Formatter (SubjTree RDFLabel)+getSubjs = subjs `liftM` get++setSubjs :: SubjTree RDFLabel -> Formatter ()+setSubjs sl = modify $ \st -> st { subjs = sl }++getProps :: Formatter (PredTree RDFLabel)+getProps = props `liftM` get++setProps :: PredTree RDFLabel -> Formatter ()+setProps ps = modify $ \st -> st { props = ps }++{-+getObjs :: Formatter ([RDFLabel])+getObjs = objs `liftM` get++setObjs :: [RDFLabel] -> Formatter ()+setObjs os = do+ st <- get+ put $ st { objs = os }+-}++getBnodesCheck :: Formatter [RDFLabel]+getBnodesCheck = bNodesCheck `liftM` get++{-+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 = mapDelete fa fn,+ formQueue = (fn,fv) : formQueue st+ }+ case mapFindMaybe fn fa of+ Nothing -> return ()+ Just v -> put (newState v) >> return ()++{-+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 fa = formAvail st+ newState = st { formAvail=mapDelete fa fn }+ case mapFindMaybe fn fa of+ Nothing -> return Nothing+ Just fv -> put newState >> return (Just fv)++{-+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++-}++-- list has a length of 1+len1 :: [a] -> Bool+len1 (_:[]) = True+len1 _ = False++{-|+Given a set of statements and a label, return the details of the+RDF collection referred to by label, or Nothing.++For label to be considered as representing a collection we require the+following conditions to hold (this is only to support the+serialisation using the '(..)' syntax and does not make any statement+about semantics of the statements with regard to RDF Collections):++ - there must be one rdf_first and one rdfRest statement+ - there must be no other predicates for the label++-} +getCollection :: + SubjTree RDFLabel -- ^ statements organized by subject+ -> RDFLabel -- ^ does this label represent a list?+ -> Maybe (SubjTree RDFLabel, [RDFLabel], [RDFLabel])+ -- ^ the statements with the elements removed; the+ -- content elements of the collection (the objects of the rdf:first+ -- predicate) and the nodes that represent the spine of the+ -- collection (in reverse order, unlike the actual contents which are in+ -- order).+getCollection subjList lbl = go subjList lbl ([],[]) + where+ go sl l (cs,ss) | l == resRdfNil = Just (sl, reverse cs, ss)+ | otherwise = do+ (pList1, sl') <- removeItem sl l+ (pFirst, pList2) <- removeItem pList1 resRdfFirst+ (pNext, pList3) <- removeItem pList2 resRdfRest++ -- QUS: could I include these checks implicitly in the pattern matches above?+ -- ie instrad of (pFirst, pos1) <- ..+ -- have ([content], pos1) <- ...+ -- ?+ if and [len1 pFirst, len1 pNext, null pList3]+ then go sl' (head pNext) (head pFirst : cs, l : ss)+ else Nothing++{-+TODO:++Should we change the preds/objs entries as well?++-}+extractList :: LabelContext -> RDFLabel -> Formatter (Maybe [RDFLabel])+extractList lctxt ln = do+ osubjs <- getSubjs+ oprops <- getProps+ let mlst = getCollection osubjs' ln++ -- we only want to send in rdf:first/rdf:rest here+ fprops = filter ((`elem` [resRdfFirst, resRdfRest]) . fst) oprops++ osubjs' =+ case lctxt of+ SubjContext -> (ln, fprops) : osubjs+ _ -> osubjs ++ -- tr = "extractList " ++ show ln ++ " (" ++ show lctxt ++ ")\n -> osubjs= " ++ show osubjs ++ "\n -> opreds= " ++ show oprops ++ "\n -> mlst= " ++ show mlst ++ "\n"+ -- addTrace tr+ case mlst of+ -- sl is guaranteed to be free of (ln,fprops) here if lctxt is SubjContext+ Just (sl,ls,_) -> do+ setSubjs sl+ when (lctxt == SubjContext) $ setProps $ filter ((`notElem` [resRdfFirst, resRdfRest]) . fst) oprops+ return (Just ls)++ Nothing -> return Nothing+ +{-|+Removes the first occurrence of the item from the+association list, returning it's contents and the rest+of the list, if it exists.+-}+removeItem :: (Eq a) => [(a,b)] -> a -> Maybe (b, [(a,b)])+removeItem os x =+ let (as, bs) = break (\a -> fst a == x) os+ in case bs of+ ((_,b):bbs) -> Just (b, as ++ bbs)+ [] -> Nothing++----------------------------------------------------------------------+-- Define a top-level formatter function:+----------------------------------------------------------------------++formatGraphAsText :: RDFGraph -> T.Text+formatGraphAsText = L.toStrict . formatGraphAsLazyText++formatGraphAsLazyText :: RDFGraph -> L.Text+formatGraphAsLazyText = B.toLazyText . formatGraphAsBuilder+ +formatGraphAsBuilder :: RDFGraph -> B.Builder+formatGraphAsBuilder = formatGraphIndent "\n" True+ +formatGraphIndent :: B.Builder -> Bool -> RDFGraph -> 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, Int, [String])+formatGraphDiag indnt flag gr = + let fg = formatGraph indnt " .\n" False flag gr+ ngs = emptyNgs {+ prefixes = emptyLookupMap,+ nodeGen = findMaxBnode gr+ }+ + (out, fgs) = runState fg (emptyN3FS 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 ind end dobreak dopref gr = do+ setIndent ind+ setLineBreak dobreak+ setGraph gr+ + fp <- if dopref+ then formatPrefixes (getNamespaces gr)+ else return mempty+ more <- moreSubjects+ if more+ then do+ fr <- formatSubjects+ return $ mconcat [fp, fr, end]+ else return fp++formatPrefixes :: NamespaceMap -> Formatter B.Builder+formatPrefixes pmap = do+ let mls = map (pref . keyVal) (listLookupMap pmap)+ ls <- sequence mls+ return $ mconcat ls+ where+ pref (Just p,u) = nextLine $ mconcat ["@prefix ", B.fromText p, ": <", quoteB True (show u), "> ."]+ pref (_,u) = nextLine $ mconcat ["@prefix : <", quoteB True (show u), "> ."]++{-+NOTE:+I expect there to be confusion below where I need to+convert from Text to Builder+-}++formatSubjects :: Formatter B.Builder+formatSubjects = do+ sb <- nextSubject+ sbstr <- formatLabel SubjContext sb+ + flagP <- moreProperties+ if flagP+ then do+ prstr <- formatProperties sb sbstr+ flagS <- moreSubjects+ if flagS+ then do+ fr <- formatSubjects+ return $ mconcat [prstr, " .", fr]+ else return prstr+ + else do+ txt <- nextLine sbstr+ + flagS <- moreSubjects+ if flagS+ then do+ fr <- formatSubjects+ return $ mconcat [txt, " .", fr]+ else return txt++{-+TODO: now we are throwing a Builder around it is awkward to+get the length of the text to calculate the indentation++So++ a) change the indentation scheme+ b) pass around text instead of builder++mkIndent :: L.Text -> L.Text+mkIndent inVal = L.replicate (L.length inVal) " "+-}++hackIndent :: B.Builder+hackIndent = " "++formatProperties :: RDFLabel -> B.Builder -> Formatter B.Builder+formatProperties sb sbstr = do+ pr <- nextProperty sb+ prstr <- formatLabel PredContext pr+ obstr <- formatObjects sb pr $ mconcat [sbstr, " ", prstr]+ more <- moreProperties+ let sbindent = hackIndent -- mkIndent sbstr+ if more+ then do+ fr <- formatProperties sb sbindent+ nl <- nextLine $ obstr `mappend` " ;"+ return $ nl `mappend` fr+ else nextLine obstr++formatObjects :: RDFLabel -> RDFLabel -> B.Builder -> Formatter B.Builder+formatObjects sb pr prstr = do+ ob <- nextObject sb pr+ obstr <- formatLabel ObjContext ob+ more <- moreObjects+ if more+ then do+ let prindent = hackIndent -- mkIndent prstr+ fr <- formatObjects sb pr prindent+ nl <- nextLine $ mconcat [prstr, " ", obstr, ","]+ return $ nl `mappend` fr+ else return $ mconcat [prstr, " ", obstr]++insertFormula :: RDFGraph -> Formatter B.Builder+insertFormula gr = do+ ngs0 <- getNgs+ ind <- getIndent+ let grm = formatGraph (ind `mappend` " ") "" True False+ (setNamespaces emptyNamespaceMap gr)++ (f3str, fgs') = runState grm (emptyN3FS ngs0)++ setNgs (nodeGenSt fgs')+ f4str <- nextLine " } "+ return $ mconcat [" { ",f3str, f4str]++{-+Add a list inline. We are given the labels that constitute+the list, in order, so just need to display them surrounded+by ().+-}+insertList :: [RDFLabel] -> Formatter B.Builder+insertList [] = return "()" -- not convinced this can happen+insertList xs = do+ ls <- mapM (formatLabel ObjContext) xs+ return $ mconcat ("( " : intersperse " " ls) `mappend` " )"+ +{-+Add a blank node inline.+-}++insertBnode :: LabelContext -> RDFLabel -> Formatter B.Builder+insertBnode SubjContext lbl = do+ flag <- moreProperties+ txt <- if flag+ then (`mappend` "\n") `liftM` formatProperties lbl ""+ else return ""++ -- TODO: handle indentation?+ return $ mconcat ["[", txt, "]"]++insertBnode _ lbl = do+ ost <- get+ let osubjs = subjs ost+ oprops = props ost+ oobjs = objs ost++ (bsubj, rsubjs) = partition ((== lbl) . fst) osubjs++ rprops = case bsubj of+ [(_,rs)] -> rs+ _ -> []++ -- we essentially want to create a new subgraph+ -- for this node but it's not as simple as that since+ -- we could have something like+ -- :a :b [ :foo [ :bar "xx" ] ]+ -- so we still need to carry around the whole graph+ --+ nst = ost { subjs = rsubjs,+ props = rprops,+ objs = []+ }++ put nst+ flag <- moreProperties+ txt <- if flag+ then (`mappend` "\n") `liftM` formatProperties lbl ""+ else return ""++ -- TODO: how do we restore the original set up?+ -- I can't believe the following is sufficient+ --+ nst' <- get+ let slist = map fst $ subjs nst'+ nsubjs = filter (\(l,_) -> l `elem` slist) osubjs++ put $ nst' { subjs = nsubjs,+ props = oprops, + objs = oobjs+ }++ -- TODO: handle indentation?+ return $ mconcat ["[", txt, "]"]+ +----------------------------------------------------------------------+-- Formatting helpers+----------------------------------------------------------------------++setGraph :: RDFGraph -> Formatter ()+setGraph gr = do+ st <- get++ let ngs0 = nodeGenSt st+ pre' = mapMerge (prefixes ngs0) (getNamespaces gr)+ ngs' = ngs0 { prefixes = pre' }+ arcs = sortArcs $ getArcs gr+ nst = st { graph = gr+ , subjs = arcTree arcs+ , props = []+ , objs = []+ , formAvail = getFormulae gr+ , nodeGenSt = ngs'+ , bNodesCheck = countBnodes arcs+ }++ put nst++hasMore :: (N3FormatterState -> [b]) -> Formatter Bool+hasMore lens = (not . null . lens) `liftM` get++moreSubjects :: Formatter Bool+moreSubjects = hasMore subjs+-- moreSubjects = (not . null . subjs) `liftM` get++moreProperties :: Formatter Bool+moreProperties = hasMore props+-- moreProperties = (not . null . props) `liftM` get++moreObjects :: Formatter Bool+moreObjects = hasMore objs+-- moreObjects = (not . null . objs) `liftM` get++nextSubject :: Formatter RDFLabel+nextSubject = do+ st <- get++ let sb:sbs = subjs st+ nst = st { subjs = sbs+ , props = snd sb+ , objs = []+ }++ put nst+ return $ fst sb++nextProperty :: RDFLabel -> Formatter RDFLabel+nextProperty _ = do+ st <- get++ let pr:prs = props st+ nst = st { props = prs+ , objs = snd pr+ }++ put nst+ return $ fst pr++nextObject :: RDFLabel -> RDFLabel -> Formatter RDFLabel+nextObject _ _ = do+ st <- get++ let ob:obs = objs st+ nst = st { objs = obs }++ put nst+ return ob++nextLine :: B.Builder -> Formatter B.Builder+nextLine str = do+ ind <- getIndent+ brk <- getLineBreak+ if brk+ then return $ ind `mappend` str+ else do+ -- After first line, always insert line break+ setLineBreak True+ return str++-- 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 <uri>+-- (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.+-}+formatLabel lctxt lab@(Blank (_:_)) = do+ mlst <- extractList lctxt lab+ case mlst of+ Just lst -> insertList lst+ Nothing -> do+ mfml <- extractFormula lab+ case mfml of+ Just fml -> insertFormula fml+ Nothing -> do+ nb1 <- getBnodesCheck+ 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 <- getPrefixes+ let nsuri = getScopeURI sn+ local = getScopeLocal sn+ premap = reverseLookupMap pr :: RevNamespaceMap+ prefix = mapFindMaybe nsuri premap+ + name = case prefix of+ Just (Just p) -> B.fromText $ quoteT True $ mconcat [p, ":", local] -- TODO: what are quoting rules for QNames+ _ -> mconcat ["<", quoteB True (show nsuri ++ T.unpack local), ">"]+ + {-+ name = case prefix of+ Just p -> quoteB True (p ++ ":" ++ local) -- TODO: what are quoting rules for QNames+ _ -> mconcat ["<", quoteB True (nsuri++local), ">"]+ -}+ + queueFormula lab+ return name++-- The canonical notation for xsd:double in XSD, with an upper-case E,+-- does not match the syntax used in N3, so we need to convert here. +-- Rather than converting back to a Double and then displaying that +-- we just convert E to e for now. +-- +formatLabel _ (Lit lit (Just dtype)) + | dtype == xsdDouble = return $ B.fromText $ T.toLower lit+ | dtype `elem` [xsdBoolean, xsdDecimal, xsdInteger] = return $ B.fromText lit+ | otherwise = return $ quoteText lit `mappend` formatAnnotation dtype+formatLabel _ (Lit lit Nothing) = return $ quoteText lit++formatLabel _ lab = return $ B.fromString $ show lab++-- the annotation for a literal (ie type or language)+formatAnnotation :: ScopedName -> B.Builder+formatAnnotation a | isLang a = "@" `mappend` B.fromText (langTag a)+ | otherwise = "^^" `mappend` showScopedName a++{-+We have to decide whether to use " or """ to quote+the string.++There is also no need to restrict the string to the+ASCII character set; this could be an option but we+can also leave Unicode as is (or at least convert to UTF-8).++If we use """ to surround the string then we protect the+last character if it is a " (assuming it isn't protected).+-}++quoteText :: T.Text -> B.Builder+quoteText txt = + let st = T.unpack txt -- TODO: fix+ qst = quoteB (n==1) st+ n = if '\n' `elem` st || '"' `elem` st then 3 else 1+ qch = B.fromString (replicate n '"')+ in mconcat [qch, qst, qch]++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 lab = do+ ngs <- getNgs+ let cmap = nodeMap ngs+ cval = nodeGen ngs+ nv <- case mapFind 0 lab cmap of+ 0 -> do + let nval = succ cval+ nmap = mapAdd cmap (lab, nval)+ setNgs $ ngs { nodeGen = nval, nodeMap = nmap }+ return nval+ + n -> return n+ + -- TODO: is this what we want?+ return $ "_:swish" `mappend` B.fromString (show nv)++-- TODO: need to be a bit more clever with this than we did in NTriples+-- not sure the following counts as clever enough ...+-- +showScopedName :: ScopedName -> B.Builder+{-+showScopedName (ScopedName n l) = + let uri = nsURI n ++ l+ in quote uri+-}+showScopedName = quoteB True . show++----------------------------------------------------------------------+-- Graph-related helper functions+----------------------------------------------------------------------++newtype SortedArcs lb = SA [Arc lb]++sortArcs :: (Ord lb) => [Arc lb] -> SortedArcs lb+sortArcs = SA . sort++-- Rearrange a list of arcs into a tree of pairs which group together+-- all statements for a single subject, and similarly for multiple+-- objects of a common predicate.+--+arcTree :: (Eq lb) => SortedArcs lb -> SubjTree lb+arcTree (SA as) = commonFstEq (commonFstEq id) $ map spopair as+ where+ spopair (Arc s p o) = (s,(p,o))++{-+arcTree as = map spopair $ sort as+ where+ spopair (Arc s p o) = (s,[(p,[o])])+-}++-- Rearrange a list of pairs so that multiple occurrences of the first+-- are commoned up, and the supplied function is applied to each sublist+-- with common first elements to obtain the corresponding second value+commonFstEq :: (Eq a) => ( [b] -> c ) -> [(a,b)] -> [(a,c)]+commonFstEq f ps =+ [ (fst $ head sps,f $ map snd sps) | sps <- groupBy fstEq ps ]+ where+ fstEq (f1,_) (f2,_) = f1 == f2++{-+-- Diagnostic code for checking arcTree logic:+testArcTree = (arcTree testArcTree1) == testArcTree2+testArcTree1 =+ [Arc "s1" "p11" "o111", Arc "s1" "p11" "o112"+ ,Arc "s1" "p12" "o121", Arc "s1" "p12" "o122"+ ,Arc "s2" "p21" "o211", Arc "s2" "p21" "o212"+ ,Arc "s2" "p22" "o221", Arc "s2" "p22" "o222"+ ]+testArcTree2 =+ [("s1",[("p11",["o111","o112"]),("p12",["o121","o122"])])+ ,("s2",[("p21",["o211","o212"]),("p22",["o221","o222"])])+ ]+-}+++findMaxBnode :: RDFGraph -> Int+findMaxBnode = maximum . map getAutoBnodeIndex . labels++getAutoBnodeIndex :: RDFLabel -> Int+getAutoBnodeIndex (Blank ('_':lns)) = res where+ -- cf. prelude definition of read s ...+ res = case [x | (x,t) <- reads lns, ("","") <- lex t] of+ [x] -> x+ _ -> 0+getAutoBnodeIndex _ = 0++{-+Find all blank nodes that occur+ - any number of times as a subject+ - 0 or 1 times as an object++Such nodes can be output using the "[..]" syntax. To make it simpler+to check we actually store those nodes that can not be expanded.++Note that we do not try and expand any bNode that is used in+a predicate position.++Should probably be using the SubjTree RDFLabel structure but this+is easier for now.++-}++countBnodes :: SortedArcs RDFLabel -> [RDFLabel]+countBnodes (SA as) = snd (foldl' ctr ([],[]) as)+ where+ -- first element of tuple are those blank nodes only seen once,+ -- second element those blank nodes seen multiple times+ --+ inc b@(b1s,bms) l@(Blank _) | l `elem` bms = b+ | l `elem` b1s = (delete l b1s, l:bms)+ | otherwise = (l:b1s, bms)+ inc b _ = b++ -- if the bNode appears as a predicate we instantly add it to the+ -- list of nodes not to expand, even if only used once+ incP b@(b1s,bms) l@(Blank _) | l `elem` bms = b+ | l `elem` b1s = (delete l b1s, l:bms)+ | otherwise = (b1s, l:bms)+ incP b _ = b++ ctr orig (Arc _ p o) = inc (incP orig p) o++--------------------------------------------------------------------------------+--+-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/N3Parser.hs view
@@ -0,0 +1,1145 @@+{-# LANGUAGE OverloadedStrings #-} -- only used in 'fromMaybe "" mbase' line of parseN3++--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : N3Parser+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : OverloadedStrings+--+-- This Module implements a Notation 3 parser (see [1], [2], [3]), returning a+-- new 'RDFGraph' consisting of triples and namespace information parsed from+-- the supplied N3 input string, or an error indication.+--+-- REFERENCES:+--+-- 1 <http://www.w3.org/TeamSubmission/2008/SUBM-n3-20080114/>+-- Notation3 (N3): A readable RDF syntax,+-- W3C Team Submission 14 January 2008+--+-- 2 <http://www.w3.org/DesignIssues/Notation3.html>+-- Tim Berners-Lee's design issues series notes and description+--+-- 3 <http://www.w3.org/2000/10/swap/Primer.html>+-- Notation 3 Primer by Sean Palmer+--+-- NOTES:+--+-- 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.N3Parser+ ( 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.RDF.RDFGraph+ ( RDFGraph, RDFLabel(..)+ , ToRDFLabel(..)+ , NamespaceMap+ , LookupFormula(..) + , addArc + , setFormula+ , setNamespaces+ , emptyRDFGraph+ )++import Swish.RDF.GraphClass (arc)++import Swish.Utils.LookupMap+ ( LookupMap(..)+ , LookupEntryClass(..)+ , mapFind, mapFindMaybe, mapReplaceOrAdd, mapAdd, mapReplace )++import Swish.Utils.Namespace+ ( Namespace, makeNamespace+ , ScopedName+ , getScopeNamespace+ , getScopedNameURI+ , getScopeNamespace+ , makeURIScopedName+ , makeQNameScopedName+ , makeNSScopedName+ , nullScopedName+ )++import Swish.Utils.QName (QName)++import Swish.RDF.Vocabulary+ ( langName+ , rdfType+ , rdfFirst, rdfRest, rdfNil+ , owlSameAs, logImplies+ , xsdBoolean, xsdInteger, xsdDecimal, xsdDouble+ )++import Swish.RDF.RDFParser+ ( SpecialMap+ , ParseResult+ , runParserWithError+ -- , mapPrefix+ , prefixTable+ , specialTable+ , ignore+ , notFollowedBy+ , endBy+ , sepEndBy+ , manyTill+ , noneOf+ , char+ , ichar+ , string+ , stringT+ , symbol+ , lexeme+ , whiteSpace+ , mkTypedLit+ , hex4 + , hex8 + , appendURIs+ )++import Control.Applicative+import Control.Monad (forM_, foldM)++import Network.URI (URI(..), parseURIReference)++import Data.Char (isSpace, isDigit, ord) +import Data.Maybe (fromMaybe, fromJust)++import qualified Data.Text as T+import qualified Data.Text.Lazy as L+import Text.ParserCombinators.Poly.StateText++----------------------------------------------------------------------+-- 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 :: Int -- 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' = mapReplaceOrAdd (makeNamespace pre uri) (prefixUris st)++-- | Set name for special syntax element+setSName :: String -> ScopedName -> N3State -> N3State+setSName nam snam st = st { syntaxUris=s' }+ where+ s' = mapReplaceOrAdd (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 = mapFind 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 = mapFindMaybe 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+----------------------------------------------------------------------++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 = LookupMap [makeNamespace Nothing hashURI]+ muri = fmap (makeQNameScopedName Nothing) mbase+ smap = LookupMap $ 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)++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+ +parseTextFromText :: String -> L.Text -> Either String String+parseTextFromText s =+ parseAnyfromText (string s) Nothing++parseAltFromText :: String -> String -> L.Text -> Either String String+parseAltFromText s1 s2 =+ parseAnyfromText (string s1 <|> string s2) Nothing++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 = LookupMap 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 ++ "'"+-}++parseAbsURIrefFromText :: L.Text -> Either String URI+parseAbsURIrefFromText =+ parseAnyfromText explicitURI Nothing++parseLexURIrefFromText :: L.Text -> Either String URI+parseLexURIrefFromText =+ parseAnyfromText lexUriRef Nothing++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+ mapReplaceOrAdd for the input namespace.+ +-}+operatorLabel :: ScopedName -> N3Parser RDFLabel+operatorLabel snam = do+ st <- stGet+ let sns = getScopeNamespace snam+ opmap = prefixUris st+ pkey = entryKey sns+ pval = entryVal sns+ + rval = Res snam+ + -- the lookup and the replacement could be fused+ case mapFindMaybe pkey opmap of+ Just val | val == pval -> return rval+ | otherwise -> do+ stUpdate $ \s -> s { prefixUris = mapReplace opmap sns }+ return rval+ + _ -> do+ stUpdate $ \s -> s { prefixUris = mapAdd opmap sns }+ 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@(Lit _ (Just dtype)) | dtype `elem` [xsdBoolean, xsdInteger, xsdDecimal, xsdDouble] = do + ost <- stGet+ let stmt = arc s p o+ oldp = prefixUris ost+ ogs = graphState ost+ newp = mapReplaceOrAdd (getScopeNamespace dtype) 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 c = c >= 'a' && c <= 'z'+is09 c = c >= '0' && c <= '9'+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` "-_" || 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?+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' *> return '\t')+ <|> (char 'n' *> return '\n')+ <|> (char 'r' *> return '\r')+ <|> (char '"' *> return '"')+ <|> (char '\'' *> return '\'')+ <|> (char '\\' *> return '\\')+ <|> (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 = fmap T.pack (bracket sQuot sQuot $ many n3Character)+ +{-+tripleQUoted ::= """[^"\\]*(?:(?:\\.|"(?!""))[^"\\]*)*"""+-}+tripleQuoted :: N3Parser T.Text+tripleQuoted = tQuot *> fmap T.pack (manyTill (n3Character <|> sQuot <|> char '\n') tQuot)+ where+ -- tQuot = try (count 3 sQuot)+ tQuot = exactly 3 sQuot++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+-}++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)+ -}+ +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+ let lb = char '<'+ rb = char '>'+ + -- TODO: do the whitespace definitions match?+ ustr <- between lb rb $ many (satisfy (/= '>'))+ let uclean = filter (not . isSpace) ustr+ + case parseURIReference uclean of+ Nothing -> fail $ "Unable to convert <" ++ uclean ++ "> to a URI"+ Just uref -> do+ s <- stGet+ let base = getSUri s "base"+ either fail return $ appendURIs base uref+ +-- production from the old parser; used in SwishScript+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++-}++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?+-}++qname :: N3Parser ScopedName+qname =+ fmap (uncurry makeNSScopedName) (char ':' >> g)+ <|> (n3Name >>= fullOrLocalQName)+ where+ g = (,) <$> getDefaultPrefix <*> (n3Name <|> return "")+ +fullOrLocalQName :: T.Text -> N3Parser ScopedName+fullOrLocalQName name = + (char ':' *> fullQName name)+ <|> localQName name+ +fullQName :: T.Text -> N3Parser ScopedName+fullQName name = makeNSScopedName <$> findPrefix name <*> (n3Name <|> pure "")+ +findPrefix :: T.Text -> N3Parser Namespace+findPrefix pre = do+ st <- stGet+ case mapFindMaybe (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 makeNSScopedName <$> 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 *> pure ()++{-+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 '!' *> return addStatementRev + forwardExpr = char '^' *> return 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 $ \_ -> 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+ +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 = mkTypedLit 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 = Lit <$> n3string <*> optional dtlang+ +dtlang :: N3Parser ScopedName+dtlang = + (char '@' *> langcode)+ <|> string "^^" *> n3symbol+ -- <|> (try (string "^^") *> n3symbol)++langcode :: N3Parser ScopedName+langcode = do+ h <- many1Satisfy isaz+ mt <- optional ( L.append <$> (char '-' *> pure (L.singleton '-')) <*> many1Satisfy isaz09)+ return $ langName $ L.toStrict $ L.append h (fromMaybe L.empty mt)+ +{-+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 (mkTypedLit xsdDouble <$> n3double)+ -- try (d2s <$> n3double)+ -- <|> try (mkTypedLit xsdDecimal <$> n3decimal)+ d2s <$> n3double+ <|> mkTypedLit xsdDecimal . T.pack <$> n3decimal+ <|> mkTypedLit 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") <*> 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 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/NTFormatter.hs view
@@ -0,0 +1,247 @@+{-# LANGUAGE OverloadedStrings #-}+--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : N3Formatter+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : OverloadedStrings+--+-- This Module implements a NTriples formatter (see [1])+-- for an RDFGraph value.+--+--+-- REFERENCES:+--+-- 1 <http://www.w3.org/TR/rdf-testcases/#ntriples>+-- RDF Test Cases+-- W3C Recommendation 10 February 2004+--+--------------------------------------------------------------------------------++module Swish.RDF.NTFormatter+ ( NodeGenLookupMap+ , formatGraphAsText+ , formatGraphAsLazyText+ , formatGraphAsBuilder+ )+where++import Swish.RDF.RDFGraph+ ( RDFGraph, RDFLabel(..)+ , getArcs+ )++import Swish.RDF.GraphClass+ ( Arc(..) )++import Swish.Utils.Namespace (ScopedName, getQName)+import Swish.RDF.Vocabulary (isLang, langTag)++import Swish.Utils.LookupMap+ ( LookupMap, emptyLookupMap+ , mapFind, mapAdd+ )++import Data.Char (ord, intToDigit, toUpper)++import Control.Monad.State+import Control.Applicative ((<$>))+import Data.Monoid++-- 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.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.++-- | Node name generation state information that carries through+-- and is updated by nested formulae+type NodeGenLookupMap = LookupMap (RDFLabel,Int)++data NTFormatterState = NTFS { + ntfsNodeMap :: NodeGenLookupMap,+ ntfsNodeGen :: Int+ } deriving Show++emptyNTFS :: NTFormatterState+emptyNTFS = NTFS {+ ntfsNodeMap = emptyLookupMap,+ ntfsNodeGen = 0+ }++type Formatter a = State NTFormatterState a++-- | 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 = fst $ runState (formatGraph gr) emptyNTFS++----------------------------------------------------------------------+-- Formatting as a monad-based computation+----------------------------------------------------------------------++formatGraph :: RDFGraph -> Formatter B.Builder+formatGraph gr = mconcat <$> mapM formatArc (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]+ -- return $ sl `mappend` $ space `mappend` $ pl `mappend` $ space `mappend` $ ol `mappend` nl+ +{-+If we have a blank node then can++ - use the label it contains+ - generate a new one on output++For now we create new labels whatever the input was since this+simplifies things, but it may be changed.++formatLabel :: RDFLabel -> Formatter String+formatLabel lab@(Blank (lnc:_)) = + if isDigit lnc then mapBlankNode lab else return $ show lab+formatLabel lab = return $ show lab+-}++squote, at, carets :: B.Builder+squote = "\""+at = "@"+carets = "^^"++formatLabel :: RDFLabel -> Formatter B.Builder+formatLabel lab@(Blank _) = mapBlankNode lab+formatLabel (Res sn) = return $ showScopedName sn+formatLabel (Lit lit Nothing) = return $ quoteText lit+formatLabel (Lit lit (Just nam)) | isLang nam = return $ mconcat [quoteText lit, at, B.fromText (langTag nam)]+ | otherwise = return $ mconcat [quoteText lit, carets, showScopedName nam]++-- 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 lab = do+ st <- get+ let cmap = ntfsNodeMap st+ cval = ntfsNodeGen st++ nv <- case mapFind 0 lab cmap of+ 0 -> do+ let nval = succ cval+ nmap = mapAdd cmap (lab, nval)++ put $ st { ntfsNodeMap = nmap, ntfsNodeGen = nval }+ return nval++ n -> return n++ return $ "_:swish" `mappend` B.fromString (show nv)++-- TODO: can we use Network.URI to protect the URI?+showScopedName :: ScopedName -> B.Builder+{-+showScopedName (ScopedName n l) = + let uri = T.pack (show (nsURI n)) `mappend` l+ in mconcat ["<", B.fromText (quote uri), ">"]+-}+-- showScopedName s = mconcat ["<", B.fromText (quote (T.pack (show (getQName s)))), ">"]+showScopedName s = B.fromText (quote (T.pack (show (getQName s)))) -- looks like qname already adds the <> around this++{-+Swish.Utils.MiscHelpers contains a quote routine+which we expand upon here to match the NT syntax.+-}++quoteText :: T.Text -> B.Builder+quoteText st = mconcat [squote, B.fromText (quote st), squote]++{-+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 v = go [] v+ 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 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/NTParser.hs view
@@ -0,0 +1,427 @@+--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : NTParser+-- Copyright : (c) 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : H98+--+-- This Module implements a NTriples parser (see [1]), returning a+-- new 'RDFGraph' consisting of triples and namespace information parsed from+-- the supplied NTriples input string, or an error indication.+--+-- REFERENCES:+--+-- 1 <http://www.w3.org/TR/rdf-testcases/#ntriples>+-- RDF Test Cases+-- W3C Recommendation 10 February 2004+--+--------------------------------------------------------------------------------++module Swish.RDF.NTParser+ ( ParseResult+ , parseNT + , parsefromString+ + {-+ -- * Exports for parsers that embed NTriples in a bigger syntax+ , NTParser, NTState(..)+ , ntripleDoc+ , line, ws, comment, eoln+ , character, name, triple+ , subject, predicate, object+ , uriref, urirefLbl+ , nodeID, literal, language+ -}+ + )+where++import Swish.RDF.RDFGraph+ ( RDFGraph, RDFLabel(..)+ , addArc + , emptyRDFGraph+ )++import Swish.RDF.GraphClass (arc)++import Swish.Utils.Namespace (ScopedName, makeURIScopedName)++import Swish.RDF.Vocabulary (langName)++import Swish.RDF.RDFParser ( ParseResult+ , runParserWithError+ , ignore+ , skipMany+ , noneOf+ , char+ , string+ , eoln+ , fullStop+ , hex4+ , hex8+ )+ +{-+import Swish.RDF.RDFParser+ ( ParseResult, RDFParser+ , ignore+ , annotateParsecError+ )+-}++import Control.Applicative++import Network.URI (parseURI)++import qualified Data.Text as T+import qualified Data.Text.Lazy as L++import Data.Char (ord) +import Data.Maybe (fromMaybe)++import Text.ParserCombinators.Poly.StateText++----------------------------------------------------------------------+-- 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.+--+parsefromString :: + NTParser a -- ^ parser to apply+ -> String -- ^ input to be parsed+ -> Either String a+parsefromString parser = parsefromText parser . L.pack++-- | Function to supply initial context and parse supplied term.+--+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")++{-+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 c = c >= 'a' && c <= 'z'+isAZ c = c >= 'A' && c <= 'Z'+is09 c = c >= '0' && c <= '9'++isHeadChar, isBodyChar :: Char -> Bool+isHeadChar c = isaz c || isAZ c+isBodyChar c = isHeadChar c || is09 c++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 = + {- tryin to be fancy but addStatement is a Parser not a pure function+ addStatement + <$> (subject <* skip1WS)+ <*> (predicate <* skip1WS)+ <*> (object <* (skipWS *> fullStop *> skipWS))+ -}+ + 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 in section URI References ++-}++uriref :: NTParser ScopedName+uriref = do+ -- not ideal, as want to reject invalid characters immediately rather than via parseURI+ ustr <- L.unpack <$> bracket (char '<') (char '>') (many1Satisfy (/= '>'))+ -- ustr <- bracket (char '<') (char '>') $ many1 character -- looks like need to exclude > from character+ -- ustr <- char '<' *> manyTill character (char '>')+ + maybe (failBad ("Invalid URI: <" ++ ustr ++ ">"))+ (return . makeURIScopedName)+ (parseURI ustr)++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 = Lit <$> (T.pack <$> ntstring) <*> optional dtlang++ntstring :: NTParser String+ntstring = bracket (char '"') (char '"') (many character)++dtlang :: NTParser ScopedName+dtlang = + (char '@' *> language)+ <|> (string "^^" *> uriref)++language :: NTParser ScopedName+language = do+ h <- many1Satisfy isaz+ mt <- optional ( L.cons <$> char '-' <*> many1Satisfy (\c -> isaz c || is09 c) )+ return $ langName $ L.toStrict $ L.append h $ fromMaybe L.empty mt++{-+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' *> return '\t')+ <|> (char 'n' *> return '\n')+ <|> (char 'r' *> return '\r')+ <|> (char '"' *> return '"')+ <|> (char '\\' *> return '\\')+ <|> (char 'u' *> hex4)+ <|> (char 'U' *> hex8)++character :: NTParser Char+character = + (char '\\' *> protectedChar)+ <|> satisfy isAsciiChar++--------------------------------------------------------------------------------+--+-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/Proof.hs view
@@ -0,0 +1,296 @@+--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : Proof+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : H98+--+-- 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.RDF.Proof+ ( Proof(..), Step(..)+ , checkProof, explainProof, checkStep, showProof, showsProof, showsFormula )+where++import Swish.RDF.Ruleset (Ruleset(..))++import Swish.RDF.Rule+ ( Expression(..), Formula(..), Rule(..)+ , showsFormula, showsFormulae )++import Swish.Utils.ShowM (ShowM(..))++import Swish.Utils.ListHelpers (subset)++import Data.List (union, intersect, intercalate, foldl')+import Data.Maybe (catMaybes, isNothing)++------------------------------------------------------------+-- 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 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) => 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) => [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 :: + (Expression 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) => 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) => [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 :: + (Expression 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 (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 :: + (ShowM 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 :: + (ShowM 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 :: (ShowM 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 :: (ShowM 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 [] = "<nothing>"+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 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/RDFDatatype.hs view
@@ -0,0 +1,202 @@+--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : RDFDatatype+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 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.RDFDatatype+ ( RDFDatatype+ , RDFDatatypeVal+ , RDFDatatypeMod+ , RDFModifierFn, RDFApplyModifier+ , makeRdfDtOpenVarBindingModify, makeRdfDtOpenVarBindingModifiers+ , applyRDFDatatypeMod+ , RDFDatatypeSub+ , fromRDFLabel, toRDFLabel, makeDatatypedLiteral+ )+where++import Swish.RDF.RDFGraph+ ( RDFLabel(..)+ , isDatatyped+ , getLiteralText+ , RDFGraph+ )++import Swish.RDF.RDFVarBinding+ ( RDFVarBinding, RDFOpenVarBindingModify )++import Swish.RDF.Datatype+ ( Datatype -- , typeName, typeRules+ , DatatypeVal(..)+ , DatatypeMap(..)+ , DatatypeMod(..), ModifierFn+ , ApplyModifier+ , DatatypeSub(..)+ )++import Swish.RDF.VarBinding (VarBindingModify(..))+import Swish.Utils.Namespace (ScopedName)++import Control.Monad (liftM)+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 =+ liftM (makeDatatypedLiteral dtnam) . mapV2L dtmap+ where+ dtnam = tvalName dtv+ dtmap = tvalMap dtv++-- | Create a typed literal from the given value.+makeDatatypedLiteral :: ScopedName -> T.Text -> RDFLabel+makeDatatypedLiteral dtnam txtval =+ Lit txtval (Just dtnam)++--------------------------------------------------------------------------------+--+-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/RDFDatatypeXsdInteger.hs view
@@ -0,0 +1,539 @@+{-# LANGUAGE OverloadedStrings #-}++--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : RDFDatatypeXsdInteger+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : OverloadedStrings+--+-- This module defines the structures used by Swish to represent and+-- manipulate RDF @xsd:integer@ datatyped literals.+--+--------------------------------------------------------------------------------++module Swish.RDF.RDFDatatypeXsdInteger+ ( rdfDatatypeXsdInteger+ , rdfDatatypeValXsdInteger+ , typeNameXsdInteger, namespaceXsdInteger+ , axiomsXsdInteger, rulesXsdInteger+ , prefixXsdInteger+ )+where++import Swish.RDF.RDFRuleset+ ( RDFFormula, RDFRule, RDFRuleset + , makeRDFGraphFromN3Builder+ , makeRDFFormula+ )++import Swish.RDF.RDFDatatype+ ( RDFDatatype+ , RDFDatatypeVal+ , RDFDatatypeMod+ , makeRdfDtOpenVarBindingModifiers+ )++import Swish.RDF.ClassRestrictionRule (makeRDFDatatypeRestrictionRules)+import Swish.RDF.MapXsdInteger (mapXsdInteger)++import Swish.RDF.Datatype+ ( Datatype(..)+ , DatatypeVal(..)+ , DatatypeRel(..), DatatypeRelPr+ , altArgs+ , UnaryFnTable, unaryFnApp+ , BinaryFnTable, binaryFnApp+ , BinMaybeFnTable, binMaybeFnApp+ , DatatypeMod(..) + , makeVmod11inv, makeVmod11+ , makeVmod21inv, makeVmod21+ , makeVmod20+ , makeVmod22+ )++import Swish.RDF.Ruleset (makeRuleset)++import Swish.Utils.Namespace (Namespace, ScopedName, namespaceToBuilder, makeNSScopedName)++import Swish.RDF.Vocabulary+ ( namespaceRDF+ , namespaceRDFS+ , namespaceRDFD+ , namespaceXSD+ , namespaceXsdType+ )++import Data.Monoid(Monoid(..))+import Control.Monad (liftM)+import Data.Maybe (maybeToList)++import qualified Data.Text as T+import qualified Data.Text.Lazy.Builder as B++------------------------------------------------------------+-- Misc values+------------------------------------------------------------++-- Local name for Integer datatype+nameXsdInteger :: T.Text+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++-- Compose with function of two arguments+c2 :: (b -> c) -> (a -> d -> b) -> a -> d -> c+c2 = (.) . (.)++-- 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++------------------------------------------------------------+-- Declare exported RDFDatatype value for xsd:integer+------------------------------------------------------------++rdfDatatypeXsdInteger :: RDFDatatype+rdfDatatypeXsdInteger = Datatype rdfDatatypeValXsdInteger++------------------------------------------------------------+-- 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+-- (<http://www.w3.org/2000/10/swap/doc/CwmBuiltins.html>).+--+relXsdInteger :: [DatatypeRel Integer]+relXsdInteger =+ [ relXsdIntegerAbs+ , relXsdIntegerNeg+ , relXsdIntegerSum+ , relXsdIntegerDiff+ , relXsdIntegerProd+ , relXsdIntegerDivMod+ , relXsdIntegerPower+ , relXsdIntegerEq+ , relXsdIntegerNe+ , relXsdIntegerLt+ , relXsdIntegerLe+ , relXsdIntegerGt+ , relXsdIntegerGe+ ]++mkIntRel2 ::+ T.Text -> DatatypeRelPr Integer -> UnaryFnTable Integer+ -> DatatypeRel Integer+mkIntRel2 nam pr fns = DatatypeRel+ { dtRelName = makeNSScopedName namespaceXsdInteger nam+ , dtRelFunc = altArgs pr fns unaryFnApp+ }++mkIntRel3 ::+ T.Text -> DatatypeRelPr Integer -> BinaryFnTable Integer+ -> DatatypeRel Integer+mkIntRel3 nam pr fns = DatatypeRel+ { dtRelName = makeNSScopedName namespaceXsdInteger nam+ , dtRelFunc = altArgs pr fns binaryFnApp+ }++mkIntRel3maybe ::+ T.Text -> 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, [ ] )+ ]++relXsdIntegerPower :: DatatypeRel Integer+relXsdIntegerPower = mkIntRel3maybe "power" (const True)+ [ ( const True, [ (liftM (:[]) `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+-- (<http://www.w3.org/2000/10/swap/doc/CwmBuiltins.html>).+--+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] = if r == 0 then [q] else []+ 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 ::+ T.Text -> (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++mkPrefix :: Namespace -> B.Builder+mkPrefix = namespaceToBuilder++prefixXsdInteger :: B.Builder+prefixXsdInteger = + mconcat+ [ mkPrefix namespaceRDF+ , mkPrefix namespaceRDFS+ , mkPrefix namespaceRDFD+ , mkPrefix namespaceXSD+ , mkPrefix namespaceXsdInteger+ ]++mkAxiom :: T.Text -> B.Builder -> RDFFormula+mkAxiom local gr =+ makeRDFFormula namespaceXsdInteger local (prefixXsdInteger `mappend` gr)++axiomsXsdInteger :: [RDFFormula]+axiomsXsdInteger =+ [ mkAxiom "dt" "xsd:integer rdf:type rdfs:Datatype ."+ ]++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 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/RDFDatatypeXsdString.hs view
@@ -0,0 +1,333 @@+{-# LANGUAGE OverloadedStrings #-}++--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : RDFDatatypeXsdString+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : OverloadedStrings+--+-- This module defines the structures used by Swish to represent and+-- manipulate RDF @xsd:string@ datatyped literals.+--+--------------------------------------------------------------------------------++-- TODO: this should convert to/from T.Text rather than String++module Swish.RDF.RDFDatatypeXsdString+ ( rdfDatatypeXsdString+ , rdfDatatypeValXsdString+ , typeNameXsdString, namespaceXsdString+ , axiomsXsdString, rulesXsdString+ , prefixXsdString+ )+ where++import Swish.RDF.RDFRuleset+ ( RDFFormula, RDFRule, RDFRuleset+ , makeRDFGraphFromN3Builder+ , makeRDFFormula+ , makeN3ClosureRule+ )++import Swish.RDF.RDFVarBinding (RDFVarBindingModify)++import Swish.RDF.RDFDatatype+ ( RDFDatatype+ , RDFDatatypeVal+ , RDFDatatypeMod+ , makeRdfDtOpenVarBindingModifiers+ )++import Swish.RDF.RDFGraph (RDFLabel(..))+import Swish.RDF.ClassRestrictionRule (makeRDFDatatypeRestrictionRules)++import Swish.RDF.Datatype+ ( Datatype(..)+ , DatatypeVal(..)+ , DatatypeMap(..)+ , DatatypeRel(..), DatatypeRelPr+ , altArgs+ , UnaryFnTable, unaryFnApp+ , DatatypeMod(..) + , makeVmod20+ )++import Swish.RDF.Ruleset (makeRuleset)+import Swish.Utils.Namespace (Namespace, ScopedName, namespaceToBuilder, makeNSScopedName)++import Swish.RDF.Vocabulary+ ( namespaceRDF+ , namespaceRDFS+ , namespaceRDFD+ , namespaceXSD+ , namespaceXsdType+ )++import Swish.RDF.VarBinding (VarBinding(..), addVarBinding, VarBindingModify(..))++import Data.Monoid(Monoid(..))++import qualified Data.Text as T+import qualified Data.Text.Lazy.Builder as B++------------------------------------------------------------+-- Misc values+------------------------------------------------------------++-- Local name for Integer datatype+nameXsdString :: T.Text+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++------------------------------------------------------------+-- Declare exported RDFDatatype value for xsd:integer+------------------------------------------------------------++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 ::+ T.Text -> 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 ::+ T.Text -> (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 :: T.Text -> B.Builder -> RDFFormula+mkAxiom local gr =+ makeRDFFormula namespaceXsdString local (prefixXsdString `mappend` gr)++axiomsXsdString :: [RDFFormula]+axiomsXsdString =+ [ mkAxiom "dt" "xsd:string rdf:type rdfs:Datatype ."+ ]++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+ app2 (Just (Lit s (Just _)))+ (Just (Lit l Nothing))+ vbind+ | s == l+ = [vbind]+ app2 (Just (Lit s (Just _)))+ Nothing+ vbind+ = [addVarBinding lvar (Lit s Nothing) vbind]+ app2 Nothing+ (Just (Lit l Nothing))+ vbind+ = [addVarBinding svar (Lit l (Just typeNameXsdString)) vbind]+ app2 _ _ _ = []++--------------------------------------------------------------------------------+--+-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/RDFGraph.hs view
@@ -0,0 +1,1396 @@+{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, TypeSynonymInstances #-}+{-# LANGUAGE OverloadedStrings #-}++--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : RDFGraph+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : FlexibleInstances, MultiParamTypeClasses, TypeSynonymInstances, OverloadedStrings+--+-- This module defines a memory-based RDF graph instance.+--+--------------------------------------------------------------------------------++------------------------------------------------------------+-- Simple labelled directed graph value+------------------------------------------------------------++module Swish.RDF.RDFGraph+ ( + -- * Labels+ RDFLabel(..), ToRDFLabel(..), FromRDFLabel(..)+ , isLiteral, isUntypedLiteral, isTypedLiteral, isXMLLiteral+ , isDatatyped, isMemberProp, isUri, isBlank, isQueryVar+ , getLiteralText, getScopedName, makeBlank+ , quote+ , quoteT+ + -- * RDF Graphs+ , RDFTriple+ , toRDFTriple, fromRDFTriple+ , NSGraph(..)+ , RDFGraph+ , toRDFGraph, emptyRDFGraph {-, updateRDFGraph-}+ , NamespaceMap, RevNamespaceMap, RevNamespace+ , emptyNamespaceMap+ , LookupFormula(..), Formula, FormulaMap, emptyFormulaMap+ , addArc, merge+ , allLabels, allNodes, remapLabels, remapLabelList+ , newNode, newNodes+ , setNamespaces, getNamespaces+ , setFormulae, getFormulae, setFormula, getFormula+ + -- * Re-export from GraphClass+ , LDGraph(..), Label (..), Arc(..)+ , arc, arcSubj, arcPred, arcObj, 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 <http://www.w3.org/TR/rdf-syntax-grammar/>; + -- the version used is \"W3C Recommendation 10 February 2004\", <http://www.w3.org/TR/2004/REC-rdf-syntax-grammar-20040210/>.+ -- + -- 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 <http://www.w3.org/TR/rdf-schema/>; the version+ -- used is \"W3C Recommendation 10 February 2004\", <http://www.w3.org/TR/2004/REC-rdf-schema-20040210/>.+ + -- *** Classes+ -- + -- | See the \"Classes\" section at <http://www.w3.org/TR/rdf-schema/#ch_classes> for more information.+ , resRdfsResource+ , resRdfsClass+ , resRdfsLiteral+ , resRdfsDatatype+ , resRdfXMLLiteral+ , resRdfProperty+ -- *** Properties+ -- + -- | See the \"Properties\" section at <http://www.w3.org/TR/rdf-schema/#ch_classes> for more information.+ , resRdfsRange+ , resRdfsDomain+ , resRdfType+ , resRdfsSubClassOf+ , resRdfsSubPropertyOf+ , resRdfsLabel+ , resRdfsComment+ -- *** Containers+ --+ -- | See the \"Container Classes and Properties\" section at <http://www.w3.org/TR/rdf-schema/#ch_containervocab>.+ , resRdfsContainer+ , resRdfBag+ , resRdfSeq + , resRdfAlt + , resRdfsContainerMembershipProperty+ , resRdfsMember+ -- *** Collections+ --+ -- | See the \"Collections\" section at <http://www.w3.org/TR/rdf-schema/#ch_collectionvocab>.+ , resRdfList + , resRdfFirst+ , resRdfRest + , resRdfNil + -- *** Reification Vocabulary + -- + -- | See the \"Reification Vocabulary\" section at <http://www.w3.org/TR/rdf-schema/#ch_reificationvocab>.+ , resRdfStatement + , resRdfSubject + , resRdfPredicate + , resRdfObject + -- *** Utility Properties + -- + -- | See the \"Utility Properties\" section at <http://www.w3.org/TR/rdf-schema/#ch_utilvocab>.+ , resRdfsSeeAlso+ , resRdfsIsDefinedBy+ , resRdfValue + + -- ** OWL + , resOwlSameAs+ + -- ** Miscellaneous + , resRdfdGeneralRestriction+ , resRdfdOnProperties, resRdfdConstraint, resRdfdMaxCardinality+ , resLogImplies+ + -- * Exported for testing+ , grMatchMap, grEq+ , mapnode, maplist+ )+ where++import Swish.Utils.Namespace+ ( Namespace, makeNamespace, getNamespaceTuple+ , getScopedNameURI+ , ScopedName+ , getScopeLocal, getScopeNamespace+ , getQName+ , makeQNameScopedName+ , makeURIScopedName+ , nullScopedName+ )++import Swish.RDF.Vocabulary+{- + ( namespaceRDF+ , langTag, isLang+ , rdfType+ , rdfFirst, rdfRest, rdfNil, rdfXMLLiteral+ , rdfsMember+ , rdfdGeneralRestriction+ , rdfdOnProperties, rdfdConstraint, rdfdMaxCardinality+ , owlSameAs, logImplies+ , xsdBoolean, xsdDecimal, xsdFloat, xsdDouble, xsdInteger+ , xsdDateTime, xsdDate + )+-}++import Swish.RDF.GraphClass+ ( LDGraph(..), Label (..)+ , Arc(..), arc, arcSubj, arcPred, arcObj, arcLabels+ , Selector )++import Swish.RDF.GraphMatch (graphMatch, LabelMap, ScopedLabel(..))++import Swish.Utils.QName (QName)+import Swish.Utils.MiscHelpers (hash)+import Swish.Utils.ListHelpers (addSetElem)++import Swish.Utils.LookupMap+ ( LookupMap(..), LookupEntryClass(..)+ , listLookupMap+ , mapFind, mapFindMaybe, mapReplaceOrAdd, mapAddIfNew+ , mapVals, mapKeys )++import qualified Data.Foldable as Foldable+import qualified Data.Traversable as Traversable++import qualified Data.Text as T+import qualified Data.Text.Read as T+-- import qualified Data.Text.Lazy as L+-- import Data.Text.Format (format)+-- import Data.Text.Buildable+-- import Data.Text.Format.Types (Only(..))++import Control.Applicative (Applicative, liftA, (<$>), (<*>))++import Network.URI (URI)++import Data.Monoid (Monoid(..))+import Data.Maybe (mapMaybe)+import Data.Char (ord, isDigit)+import Data.List (intersect, union, findIndices, foldl')+import Data.Ord (comparing)+-- import Data.Tuple (swap) not in 6.12.3+import Data.String (IsString(..))+import Data.Time (UTCTime, Day, ParseTime, parseTime, formatTime)+import System.Locale (defaultTimeLocale) +import Text.Printf++swap :: (a,b) -> (b,a)+swap (a,b) = (b,a)++-- | RDF graph node values+--+-- cf. <http://www.w3.org/TR/rdf-concepts/#section-Graph-syntax>+--+-- 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)@.+--+data RDFLabel =+ Res ScopedName -- ^ resource+ | Lit T.Text (Maybe ScopedName) -- ^ literal [type/language]+ | Blank String -- ^ blank node+ | Var String -- ^ variable (not used in ordinary graphs)+ | NoNode -- ^ no node (not used in ordinary graphs)++-- TODO: should Lit be split up so that can easily differentiate between+-- a type and a language tag?++-- | 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,+-- <http://www.w3.org/TR/2004/REC-rdf-concepts-20040210/#section-Literal-Equality>.+--+instance Eq RDFLabel where+ Res q1 == Res q2 = q1 == q2+ Blank b1 == Blank b2 = b1 == b2+ Var v1 == Var v2 = v1 == v2++ Lit s1 Nothing == Lit s2 Nothing = s1 == s2+ Lit s1 (Just t1) == Lit s2 (Just t2) = s1 == s2 && (t1 == t2 ||+ (isLang t1 && isLang t2 &&+ (T.toLower . langTag) t1 == (T.toLower . langTag) t2))+ + _ == _ = False++instance Show RDFLabel where+ show (Res sn) = show sn+ show (Lit st Nothing) = quote1Str st+ 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 = "<NoNode>"++instance Ord RDFLabel where+ -- Optimize some common cases..+ compare (Res sn1) (Res sn2) = compare sn1 sn2+ compare (Blank ln1) (Blank ln2) = compare ln1 ln2+ compare (Res _) (Blank _) = LT+ compare (Blank _) (Res _) = GT+ -- .. else use show string comparison+ compare l1 l2 = comparing show l1 l2+ + {- <= is not used if compare is provided+ -- Similarly for <=+ (Res qn1) <= (Res qn2) = qn1 <= qn2+ (Blank ln1) <= (Blank ln2) = ln1 <= ln2+ (Res _) <= (Blank _) = True+ (Blank _) <= (Res _) = False+ l1 <= l2 = show l1 <= show l2+ -}+ +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 (getScopeLocal sn)+ getLocal (NoNode) = "None"+ getLocal _ = "Lit_"+ makeLabel ('?':loc) = Var loc+ makeLabel loc = Blank loc+ labelHash seed lb = hash seed (showCanon lb)++instance IsString RDFLabel where+ fromString = flip Lit Nothing . 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+<http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#lexical-space>.+ +Note that this is very similar to+'Swish.RDF.RDFDatatype.toRDFLabel' and should be moved into+a @Swish.RDF.Datatype@ module.+-}++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 very similar to+'Swish.RDF.RDFDatatype.fromRDFLabel' and should be moved into+a @Swish.RDF.Datatype@ module.+-}++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 . id@. +instance FromRDFLabel RDFLabel where+ fromRDFLabel = Just . id+ +-- 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 (Lit xs (Just dt)) | dt == dtype = conv xs+ | otherwise = Nothing+fLabel _ _ _ = Nothing+ +tLabel :: (Show a) => ScopedName -> (String -> T.Text) -> a -> RDFLabel +tLabel dtype conv = flip Lit (Just dtype) . conv . show ++-- | The character is converted to an untyped literal of length one.+instance ToRDFLabel Char where+ toRDFLabel = flip Lit Nothing . T.singleton++-- | The label must be an untyped literal containing a single character.+instance FromRDFLabel Char where+ fromRDFLabel (Lit cs Nothing) | T.compareLength cs 1 == EQ = Just (T.head cs)+ | otherwise = Nothing+ fromRDFLabel _ = Nothing++-- | Strings are converted to untyped literals.+instance ToRDFLabel String where+ toRDFLabel = flip Lit Nothing . T.pack++-- | Only untyped literals are converted to strings.+instance FromRDFLabel String where+ fromRDFLabel (Lit xs Nothing) = 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 = Lit (if b then "true" else "false") (Just 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 Lit (Just 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 explicitlu 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"+ pt f = parseTime defaultTimeLocale f inVal+ 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 Lit (Just 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 Lit (Just 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.+-- +showCanon :: RDFLabel -> String+showCanon (Res sn) = "<"++show (getScopedNameURI sn)++">"+showCanon (Lit st (Just nam))+ | isLang nam = quote1Str st ++ "@" ++ T.unpack (langTag nam)+ | otherwise = quote1Str st ++ "^^" ++ show (getScopedNameURI nam)+showCanon s = show s++-- | See `quote`.+quoteT :: Bool -> T.Text -> T.Text+quoteT f = T.pack . quote f . T.unpack ++{-| N3-style quoting rules for a string.++TODO: when flag is `False` need to worry about multiple quotes (> 2)+in a row.+-}++quote :: + Bool -- ^ @True@ if the string is to be displayed using one rather than three quotes.+ -> String -- ^ String to quote.+ -> String+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 quotes ("...")++quote1Str :: T.Text -> String+quote1Str t = '"' : quote False (T.unpack t) ++ ['"']++---------------------------------------------------------+-- Selected RDFLabel values+---------------------------------------------------------++-- | @rdf:type@ from <http://www.w3.org/TR/rdf-schema/#ch_type>.+resRdfType :: RDFLabel+resRdfType = Res rdfType ++-- | @rdf:List@ from <http://www.w3.org/TR/rdf-schema/#ch_list>.+resRdfList :: RDFLabel+resRdfList = Res rdfList++-- | @rdf:first@ from <http://www.w3.org/TR/rdf-schema/#ch_first>.+resRdfFirst :: RDFLabel+resRdfFirst = Res rdfFirst ++-- | @rdf:rest@ from <http://www.w3.org/TR/rdf-schema/#ch_rest>.+resRdfRest :: RDFLabel+resRdfRest = Res rdfRest++-- | @rdf:nil@ from <http://www.w3.org/TR/rdf-schema/#ch_nil>.+resRdfNil :: RDFLabel+resRdfNil = Res rdfNil++-- | @rdfs:member@ from <http://www.w3.org/TR/rdf-schema/#ch_member>.+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 <http://www.w3.org/TR/rdf-schema/#ch_seealso>.+resRdfsSeeAlso :: RDFLabel+resRdfsSeeAlso = Res rdfsSeeAlso++-- | @rdf:value@ from <http://www.w3.org/TR/rdf-schema/#ch_value>.+resRdfValue :: RDFLabel+resRdfValue = Res rdfValue++-- | @owl:sameAs@.+resOwlSameAs :: RDFLabel+resOwlSameAs = Res owlSameAs++-- | @log:implies@.+resLogImplies :: RDFLabel+resLogImplies = Res logImplies++-- | @rdfs:label@ from <http://www.w3.org/TR/rdf-schema/#ch_label>.+resRdfsLabel :: RDFLabel+resRdfsLabel = Res rdfsLabel++-- | @rdfs:comment@ from <http://www.w3.org/TR/rdf-schema/#ch_comment>.+resRdfsComment :: RDFLabel+resRdfsComment = Res rdfsComment++-- | @rdf:Property@ from <http://www.w3.org/TR/rdf-schema/#ch_property>.+resRdfProperty :: RDFLabel+resRdfProperty = Res rdfProperty++-- | @rdfs:subPropertyOf@ from <http://www.w3.org/TR/rdf-schema/#ch_subpropertyof>.+resRdfsSubPropertyOf :: RDFLabel+resRdfsSubPropertyOf = Res rdfsSubPropertyOf++-- | @rdfs:subClassOf@ from <http://www.w3.org/TR/rdf-schema/#ch_subclassof>.+resRdfsSubClassOf :: RDFLabel+resRdfsSubClassOf = Res rdfsSubClassOf++-- | @rdfs:Class@ from <http://www.w3.org/TR/rdf-schema/#ch_class>.+resRdfsClass :: RDFLabel+resRdfsClass = Res rdfsClass++-- | @rdfs:Literal@ from <http://www.w3.org/TR/rdf-schema/#ch_literal>.+resRdfsLiteral :: RDFLabel+resRdfsLiteral = Res rdfsLiteral++-- | @rdfs:Datatype@ from <http://www.w3.org/TR/rdf-schema/#ch_datatype>.+resRdfsDatatype :: RDFLabel+resRdfsDatatype = Res rdfsDatatype++-- | @rdf:XMLLiteral@ from <http://www.w3.org/TR/rdf-schema/#ch_xmlliteral>.+resRdfXMLLiteral :: RDFLabel+resRdfXMLLiteral = Res rdfXMLLiteral++-- | @rdfs:range@ from <http://www.w3.org/TR/rdf-schema/#ch_range>.+resRdfsRange :: RDFLabel+resRdfsRange = Res rdfsRange++-- | @rdfs:domain@ from <http://www.w3.org/TR/rdf-schema/#ch_domain>.+resRdfsDomain :: RDFLabel+resRdfsDomain = Res rdfsDomain++-- | @rdfs:Container@ from <http://www.w3.org/TR/rdf-schema/#ch_container>.+resRdfsContainer :: RDFLabel+resRdfsContainer = Res rdfsContainer++-- | @rdf:Bag@ from <http://www.w3.org/TR/rdf-schema/#ch_bag>.+resRdfBag :: RDFLabel+resRdfBag = Res rdfBag++-- | @rdf:Seq@ from <http://www.w3.org/TR/rdf-schema/#ch_seq>.+resRdfSeq :: RDFLabel+resRdfSeq = Res rdfSeq++-- | @rdf:Alt@ from <http://www.w3.org/TR/rdf-schema/#ch_alt>.+resRdfAlt :: RDFLabel+resRdfAlt = Res rdfAlt++-- | @rdfs:ContainerMembershipProperty@ from <http://www.w3.org/TR/rdf-schema/#ch_containermembershipproperty>.+resRdfsContainerMembershipProperty :: RDFLabel+resRdfsContainerMembershipProperty = Res rdfsContainerMembershipProperty++-- | @rdfs:isDefinedBy@ from <http://www.w3.org/TR/rdf-schema/#ch_isdefinedby>.+resRdfsIsDefinedBy :: RDFLabel+resRdfsIsDefinedBy = Res rdfsIsDefinedBy++-- | @rdfs:Resource@ from <http://www.w3.org/TR/rdf-schema/#ch_resource>.+resRdfsResource :: RDFLabel+resRdfsResource = Res rdfsResource++-- | @rdf:Statement@ from <http://www.w3.org/TR/rdf-schema/#ch_statement>.+resRdfStatement :: RDFLabel+resRdfStatement = Res rdfStatement++-- | @rdf:subject@ from <http://www.w3.org/TR/rdf-schema/#ch_subject>.+resRdfSubject :: RDFLabel+resRdfSubject = Res rdfSubject++-- | @rdf:predicate@ from <http://www.w3.org/TR/rdf-schema/#ch_predicate>.+resRdfPredicate :: RDFLabel+resRdfPredicate = Res rdfPredicate++-- | @rdf:object@ from <http://www.w3.org/TR/rdf-schema/#ch_object>.+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.+resRdfn :: Int -> 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+isLiteral :: RDFLabel -> Bool+isLiteral (Lit _ _) = True+isLiteral _ = False++-- |Test if supplied labal is an untyped literal node+isUntypedLiteral :: RDFLabel -> Bool+isUntypedLiteral (Lit _ Nothing ) = True+isUntypedLiteral (Lit _ (Just tn)) = isLang tn+isUntypedLiteral _ = False++-- |Test if supplied labal is an untyped literal node+isTypedLiteral :: RDFLabel -> Bool+isTypedLiteral (Lit _ (Just tn)) = not (isLang tn)+isTypedLiteral _ = False++-- |Test if supplied labal is an XML literal node+isXMLLiteral :: RDFLabel -> Bool+isXMLLiteral = isDatatyped rdfXMLLiteral++-- |Test if supplied label is an typed literal node of a given datatype+isDatatyped :: ScopedName -> RDFLabel -> Bool+isDatatyped d (Lit _ (Just n)) = n == d+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 (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+getLiteralText :: RDFLabel -> T.Text+getLiteralText (Lit s _) = s+getLiteralText _ = ""++-- |Extract ScopedName value from a resource node+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++-- | 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++type NamespaceMap = LookupMap Namespace++data RevNamespace = RevNamespace Namespace++instance LookupEntryClass RevNamespace URI (Maybe T.Text) where+ keyVal (RevNamespace ns) = swap $ getNamespaceTuple ns+ newEntry (uri,pre) = RevNamespace (makeNamespace pre uri)++type RevNamespaceMap = LookupMap RevNamespace++-- | Create an emoty namespace map.+emptyNamespaceMap :: NamespaceMap+emptyNamespaceMap = LookupMap []++-- | 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 (Label lb)+ => LookupEntryClass (LookupFormula lb (NSGraph lb)) lb (NSGraph lb)+ where+ keyVal fe = (formLabel fe, formGraph fe)+ newEntry (k,v) = Formula { formLabel=k, formGraph=v }++instance (Label lb) => Show (LookupFormula lb (NSGraph lb))+ where+ show (Formula l g) = show l ++ " :- { " ++ showArcs " " g ++ " }"++type Formula lb = LookupFormula lb (NSGraph lb)++type FormulaMap lb = LookupMap (LookupFormula lb (NSGraph lb))++-- | Create an empty formula map.+emptyFormulaMap :: FormulaMap RDFLabel+emptyFormulaMap = LookupMap []++{- given up on trying to do Functor for formulae...+instance Functor (LookupFormula (NSGraph lb)) where+ fmap f fm = mapTranslateEntries (mapFormulaEntry f) fm+-}++formulaeMap :: (lb -> l2) -> FormulaMap lb -> FormulaMap l2+formulaeMap f = fmap (formulaEntryMap f) ++formulaEntryMap ::+ (lb -> l2)+ -> Formula lb+ -> Formula l2+formulaEntryMap f (Formula k gr) = Formula (f k) (fmap f gr)++formulaeMapA :: Applicative f => (lb -> f l2) -> + FormulaMap lb -> f (FormulaMap l2)+formulaeMapA f = Traversable.traverse (formulaEntryMapA f)++formulaEntryMapA ::+ (Applicative f) => + (lb -> f l2)+ -> Formula lb+ -> f (Formula l2)+formulaEntryMapA f (Formula k gr) = Formula `liftA` f k <*> Traversable.traverse f gr++{-+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 :: [Arc lb] -- ^ the statements in the graph+ }++-- | 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 = mapFindMaybe l (formulae g)++-- | Add (or replace) a formula.+setFormula :: (Label lb) => Formula lb -> NSGraph lb -> NSGraph lb+setFormula f g = g { formulae=mapReplaceOrAdd f (formulae g) }++instance (Label lb) => Monoid (NSGraph lb) where+ mempty = NSGraph emptyNamespaceMap (LookupMap []) []+ mappend = merge+ +instance (Label lb) => LDGraph NSGraph lb where+ getArcs = statements + setArcs as g = g { statements=as }+ containedIn = error "containedIn for LDGraph NSGraph lb is undefined!" -- TODO: should there be one defined?++{-|+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 gr = gr { statements=addSetElem ar (statements gr) }++instance Functor NSGraph where+ fmap f (NSGraph ns fml stmts) =+ NSGraph ns (formulaeMap f fml) ((map $ fmap f) stmts)++instance Foldable.Foldable NSGraph where+ foldMap = Traversable.foldMapDefault++instance Traversable.Traversable NSGraph where+ traverse f (NSGraph ns fml stmts) = + NSGraph ns <$> formulaeMapA f fml <*> (Traversable.traverse $ Traversable.traverse f) stmts+ +instance (Label lb) => Eq (NSGraph lb) where+ (==) = grEq++instance (Label lb) => Show (NSGraph lb) where+ show = grShow ""+ showList = grShowList ""++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 = foldr ((++) . (pp ++) . show) "" fml+ fml = listLookupMap (getFormulae g)+ pp = "\n " ++ p++showArcs :: (Label lb) => String -> NSGraph lb -> String+showArcs p g = foldr ((++) . (pp ++) . show) "" (getArcs g)+ where+ pp = "\n " ++ p++grEq :: (Label lb) => NSGraph lb -> NSGraph lb -> Bool+grEq g1 = fst . grMatchMap g1++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+ mapFormula g l = mapFindMaybe 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 = allLabels labelIsVar gr1+ bn2 = allLabels labelIsVar gr2+ dupbn = intersect bn1 bn2+ allbn = union bn1 bn2+ in+ add 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 -> [lb]+allLabels p gr = 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 -> [lb]+allNodes p = unionNodes p [] . nodes++-- | List all nodes in graph formulae satisfying a supplied predicate+formulaNodes :: (Label lb) => (lb -> Bool) -> NSGraph lb -> [lb]+formulaNodes p gr = foldl' (unionNodes p) fkeys (map (allLabels p) fvals)+ where+ -- fm :: (Label lb) => FormulaMap lb+ -- LookupMap LookupFormula (NSGraph lb) lb+ fm = formulae gr+ -- fvals :: (Label lb) => [NSGraph lb]+ fvals = mapVals fm+ -- fkeys :: (Label lb) => [lb]+ fkeys = filter p $ mapKeys fm++-- | Helper to filter variable nodes and merge with those found so far+unionNodes :: (Label lb) => (lb -> Bool) -> [lb] -> [lb] -> [lb]+unionNodes p ls1 ls2 = ls1 `union` filter p ls2++-- |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 = fmap (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 =+ mapFind nv nv (LookupMap (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++-- |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.)+--+-- TODO: optimize this for common case @nnn@ and @_nnn@:+-- always generate @_nnn@ and keep track of last allocated+--+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)++noderootindex :: (Label lb) => lb -> (String,Int)+noderootindex dn = (nh,nx) where+ (nh,nt) = splitnodeid $ getLocal dn+ nx = if null nt then 0 else read nt++splitnodeid :: String -> (String,String)+splitnodeid dn = splitAt (tx+1) dn where+ tx = last $ (-1):findIndices (not . isDigit) dn++trynodes :: (Label lb) => (String,Int) -> [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 list 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 :: [RDFTriple] -> RDFGraph+-- toRDFGraph arcs = emptyRDFGraph { statements = arcs }+toRDFGraph arcs = + let lbls = concatMap arcLabels arcs+ + getNS (Res s) = Just s+ getNS (Lit _ (Just tn)) | not (isLang tn) = Just tn+ | otherwise = Nothing+ getNS _ = Nothing++ ns = mapMaybe (fmap getScopeNamespace . getNS) lbls+ nsmap = foldl' mapAddIfNew emptyNamespaceMap ns+ + in mempty { namespaces = nsmap, statements = arcs }++-- |Create a new, empty RDF graph.+--+-- This uses `mempty` from the `Monoid` instance+-- of `NSGraph`.+--+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 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/RDFGraphShowM.hs view
@@ -0,0 +1,57 @@+{-# LANGUAGE TypeSynonymInstances #-}+{-# LANGUAGE FlexibleInstances #-} -- needed for ghc 7.2++--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : RDFGraphShowM+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : TypeSynonymInstances, FlexibleInstances+--+-- This module defines a `ShowM` class instance for `RDFGraph`, to be+-- used when displaying RDF Graph values as part of a proof sequence,+-- etc.+--+--------------------------------------------------------------------------------++module Swish.RDF.RDFGraphShowM() where++import Swish.RDF.RDFGraph (RDFGraph)+import Swish.RDF.N3Formatter (formatGraphIndent)+import Swish.Utils.ShowM (ShowM(..))++-- import qualified Data.Text.Lazy as L+import qualified Data.Text.Lazy.Builder as B++instance ShowM RDFGraph where+ -- showms linebreak = shows . L.unpack . B.toLazyText . formatGraphIndent linebreak False + showms linebreak = shows . formatGraphIndent (B.fromString linebreak) False ++--------------------------------------------------------------------------------+--+-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/RDFParser.hs view
@@ -0,0 +1,353 @@+{-# LANGUAGE OverloadedStrings #-}+--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : RDFParser+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : OverloadedStrings+--+-- Support for the RDF Parsing modules.+--+--------------------------------------------------------------------------------++module Swish.RDF.RDFParser+ ( 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+ , mkTypedLit+ , hex4+ , hex8+ , appendURIs+ )+ where++import Swish.RDF.RDFGraph (RDFGraph, RDFLabel(..))+import Swish.RDF.Vocabulary+ ( namespaceRDF+ , namespaceRDFS+ , namespaceRDFD+ , namespaceOWL+ , namespaceLOG+ , rdfType+ , rdfFirst, rdfRest, rdfNil+ , owlSameAs, logImplies+ , defaultBase+ )++import Swish.Utils.LookupMap (LookupMap(..))+import Swish.Utils.Namespace (Namespace, makeNamespace, ScopedName)++import qualified Data.Text as T+import qualified Data.Text.Lazy as L+import qualified Data.Text.Read as R++import Text.ParserCombinators.Poly.StateText++import Network.URI (URI(..), relativeTo, parseURIReference)++import Data.Char (isSpace, isHexDigit, chr)+import Data.Maybe (fromMaybe, fromJust)++-- Code++-- | Append the two URIs. Should probably be moved+-- out of RDFParser. It is also just a thin wrapper around+-- `Network.URI.relativeTo`.++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+ "" -> case uri `relativeTo` base of+ Just out -> Right out+ _ -> Left $ "Unable to append <" ++ show uri ++ "> to base=<" ++ show base ++ ">"+ _ -> Right uri+ +-- | Type for special name lookup table+type SpecialMap = LookupMap (String,ScopedName)++{-+-- | Lookup prefix in table and return the matching URI.+--+-- If the prefix is unknown then we currently error+-- out (used to return 'prefix:' or ':' but now using+-- URIs I am changing this behavior). This may well be+-- backed out.+mapPrefix :: NamespaceMap -> Maybe String -> URI+mapPrefix pmap pfix = + case mapFindMaybe pfix pmap of+ Just uri -> uri+ Nothing -> error $ "Unable to find prefix: " ++ show pfix -- fromMaybe "" pfix ++ ":"+-}+ +{-+mapPrefix ps p@(Just pre) = mapFind (pre++":") p ps+mapPrefix ps _ = mapFind ":" Nothing ps+-}++-- | 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.+The optional argument defines the initial base URI.+-}+specialTable :: Maybe ScopedName -> [(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++type ParseResult = Either String RDFGraph++ignore :: (Applicative f) => f a -> f ()+ignore f = f *> pure ()++char :: Char -> Parser s Char+char c = satisfy (==c)++ichar :: Char -> Parser s ()+ichar = ignore . char++-- TODO: is there a better way to do this?+string :: String -> Parser s String+string = mapM char+ +stringT :: T.Text -> Parser s T.Text+stringT s = string (T.unpack s) >> return s++skipMany :: Parser s a -> Parser s ()+skipMany = ignore . many+ +skipMany1 :: Parser s a -> Parser s ()+skipMany1 = ignore . many1+ +endBy :: Parser s a -> Parser s b -> Parser s [a]+endBy p sep = many (p <* sep)++sepEndBy :: Parser s a -> Parser s b -> Parser s [a]+sepEndBy p sep = sepEndBy1 p sep <|> pure []++-- is the separator optional?+sepEndBy1 :: Parser s a -> Parser s b -> Parser s [a]+sepEndBy1 p sep = do+ x <- p+ (sep *> ((x:) <$> sepEndBy p sep)) <|> return [x]+ +manyTill :: Parser s a -> Parser s b -> Parser s [a]+manyTill p end = go+ where+ go = (end *> return [])+ <|>+ ((:) <$> p <*> go)+++noneOf :: String -> Parser s Char +noneOf istr = satisfy (`notElem` istr)+ +fullStop :: Parser s ()+fullStop = ichar '.'++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"])+ +notFollowedBy :: (Char -> Bool) -> Parser s ()+notFollowedBy p = do+ c <- next+ if p c + then fail $ "Unexpected character: " ++ show [c]+ else reparse $ L.singleton c++symbol :: String -> Parser s String+symbol = lexeme . string++isymbol :: String -> Parser s ()+isymbol = ignore . symbol++lexeme :: Parser s a -> Parser s a+lexeme p = p <* whiteSpace++whiteSpace :: Parser s ()+whiteSpace = skipMany (simpleSpace <|> oneLineComment)++simpleSpace :: Parser s ()+simpleSpace = ignore $ many1Satisfy isSpace++oneLineComment :: Parser s ()+oneLineComment = ichar '#' *> manySatisfy (/= '\n') *> pure ()++{-++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++-}++-- | Create a typed literal.+mkTypedLit ::+ ScopedName -- ^ the type+ -> T.Text -- ^ the value+ -> RDFLabel+mkTypedLit u v = Lit v (Just u)++{-+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++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+ +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 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/RDFProof.hs view
@@ -0,0 +1,380 @@+{-# LANGUAGE FlexibleInstances, UndecidableInstances #-}+--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : RDFProof+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : 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 <http://www.w3.org/TR/rdf-mt/>.+--+--------------------------------------------------------------------------------++module Swish.RDF.RDFProof+ ( RDFProof, RDFProofStep+ , makeRDFProof, makeRDFProofStep+ , makeRdfInstanceEntailmentRule+ , makeRdfSubgraphEntailmentRule+ , makeRdfSimpleEntailmentRule )+where++import Swish.RDF.RDFQuery+ ( rdfQueryInstance+ , rdfQuerySubs + )++import Swish.RDF.RDFRuleset+ ( RDFFormula, RDFRule, RDFRuleset )++import Swish.RDF.RDFGraph+ ( RDFLabel(..), RDFGraph+ --, makeBlank+ , merge , allLabels , remapLabelList+ {-, newNode, newNodes+ , toRDFGraph -}, emptyRDFGraph+ )++import Swish.RDF.VarBinding (makeVarBinding)++import Swish.RDF.Proof+ ( Proof(..), Step(..) )++import Swish.RDF.Rule+ ( Expression(..), Rule(..) )++import Swish.Utils.Namespace (ScopedName)++import Swish.RDF.GraphClass+ ( Label(..), LDGraph(..), replaceArcs )++import Swish.Utils.LookupMap+ ( makeLookupMap, mapFind )++import Swish.Utils.ListHelpers+ ( subset+ , powerSet+ , powerSequencesLen+ , flist+ )++------------------------------------------------------------+-- 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 (Label lb, LDGraph lg lb) => Expression (lg lb) where+ isValid = null . getArcs ++------------------------------------------------------------+-- Define RDF-specific types for proof framework+------------------------------------------------------------++type RDFProof = Proof RDFGraph++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]+ -> 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 'allNodes' can be used to obtain a list of all+-- the subjects and objuects used ina 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.+-- This sequencing is determined by the powerset function used,+-- which generates subsets in increasing order of size+-- (see module 'ListHelpers').+--+-- 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 emptyRDFGraph+ else foldl1 merge ante+ -- Obtain lists of variable and non-variable nodes+ -- (was: nonvarNodes = allLabels (not . labelIsVar) mergeGraph)+ nonvarNodes = vocab+ varNodes = allLabels labelIsVar mergeGraph+ -- Obtain list of possible remappings for non-variable nodes+ mapList = remapLabelList nonvarNodes varNodes+ mapSubLists = powerSet mapList+ mapGr ls = fmap (\l -> mapFind l l (makeLookupMap 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 = 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 emptyRDFGraph -- 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.+ any (mante ==) bsubs++-- 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. This sequencing is determined+-- by the 'powerSet' function used which generates subsets in+-- increasing order of size (see module 'ListHelpers').+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 emptyRDFGraph+ else foldl1 merge ante+ in+ -- Return all subgraphs of the full graph constructed above+ map (replaceArcs mergeGraph) (init $ powerSet $ 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 emptyRDFGraph+ else foldl1 add ante+ in+ -- Check each consequent graph arc is in the antecedent graph+ getArcs cons `subset` 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 emptyRDFGraph else foldl1 add 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 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/RDFProofContext.hs view
@@ -0,0 +1,848 @@+{-# LANGUAGE OverloadedStrings #-}++--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : RDFProofContext+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : 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.RDFProofContext ( rulesetRDF + , rulesetRDFS+ , rulesetRDFD) where++import Swish.RDF.BuiltInDatatypes (findRDFDatatype)++import Swish.RDF.RDFProof+ ( makeRdfSubgraphEntailmentRule+ , makeRdfSimpleEntailmentRule )++import Swish.RDF.RDFRuleset + ( RDFFormula, RDFRule, RDFRuleset + , makeRDFFormula+ , makeN3ClosureRule+ , makeN3ClosureSimpleRule+ , makeN3ClosureModifyRule+ , makeN3ClosureAllocatorRule+ , makeNodeAllocTo )++import Swish.RDF.RDFVarBinding+ ( RDFVarBinding+ , RDFVarBindingModify+ , RDFVarBindingFilter+ , rdfVarBindingUriRef, rdfVarBindingBlank+ , rdfVarBindingLiteral+ , rdfVarBindingUntypedLiteral + , rdfVarBindingXMLLiteral, rdfVarBindingDatatyped+ , rdfVarBindingMemberProp+ )++import Swish.RDF.RDFGraph+ ( RDFLabel(..)+ , isUri, isDatatyped+ , getLiteralText )++import Swish.RDF.VarBinding+ ( applyVarBinding+ , addVarBinding+ , VarBindingModify(..)+ , makeVarFilterModify+ , varFilterDisjunction+ )++import Swish.RDF.Ruleset (makeRuleset)+import Swish.RDF.Datatype (typeMkCanonicalForm)+import Swish.Utils.Namespace (Namespace, makeNSScopedName)++import Swish.RDF.Vocabulary+ ( namespaceRDFD+ , scopeRDF+ , scopeRDFS+ , scopeRDFD+ )++import Control.Monad (liftM)+import Data.Monoid (Monoid(..))+import Data.Maybe (isJust, fromJust)++import qualified Data.Text as T+import qualified Data.Text.Lazy.Builder as B++------------------------------------------------------------+-- Define query binding filter auxiliaries+------------------------------------------------------------++makeFormula :: Namespace -> T.Text -> 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++{- -- 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 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/RDFQuery.hs view
@@ -0,0 +1,636 @@+--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : RDFQuery+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : H98+--+-- 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:+--+-- >>> :m + Swish.RDF Swish.RDF.N3Parser Swish.RDF.RDFQuery+-- >>> :set -XOverloadedStrings+-- >>> let qparse = either error id . parseN3fromText+-- >>> let igr = qparse "@prefix a: <http://example.com/>. a:a a a:A ; a:foo a:bar. a:b a a:B ; a:foo a:bar."+-- >>> let qgr = qparse "?node a ?type."+-- >>> rdfQueryFind qgr igr+-- [[(?type,a:B),(?node,a:b)],[(?type,a:A),(?node,a:a)]]+-- >>> let bn = (toRDFLabel . Data.Maybe.fromJust . Network.URI.parseURI) "http://example.com/B"+-- >>> rdfFindArcs (rdfObjEq bn) igr+-- [(a:b,rdf:type,a:B)]+-- >>> Data.Maybe.mapMaybe (flip Swish.RDF.VarBinding.vbMap (Var "type")) $ rdfQueryFind qgr igr+-- [a:B,a:A]+-- +--------------------------------------------------------------------------------++module Swish.RDF.RDFQuery+ ( rdfQueryFind, rdfQueryFilter+ , rdfQueryBack, rdfQueryBackFilter, rdfQueryBackModify+ , rdfQueryInstance+ , rdfQuerySubs, rdfQueryBackSubs+ , rdfQuerySubsAll+ , rdfQuerySubsBlank, rdfQueryBackSubsBlank+ , rdfFindArcs, rdfSubjEq, rdfPredEq, rdfObjEq+ , rdfFindPredVal, rdfFindPredInt, rdfFindValSubj+ , rdfFindList+ -- debug+ , rdfQuerySubs2 )+where++import Swish.RDF.RDFVarBinding+ ( RDFVarBinding, nullRDFVarBinding+ , RDFVarBindingFilter+ )++import Swish.RDF.RDFGraph+ ( Arc(..), LDGraph(..)+ , arcSubj, arcPred, arcObj+ , RDFLabel(..)+ , isDatatyped, isBlank, isQueryVar+ , getLiteralText, makeBlank+ , RDFTriple+ , RDFGraph, emptyRDFGraph+ , allLabels, remapLabels+ , resRdfFirst+ , resRdfRest+ , resRdfNil+ )++import Swish.RDF.MapXsdInteger (mapXsdInteger)+import Swish.RDF.Datatype (DatatypeMap(..))++import Swish.RDF.VarBinding+ ( VarBinding(..)+ , makeVarBinding+ , applyVarBinding, joinVarBindings+ , VarBindingModify(..)+ , VarBindingFilter(..)+ )++import Swish.RDF.Vocabulary (xsdInteger, xsdNonNegInteger)++import Swish.Utils.ListHelpers (listProduct, allp, anyp)++import qualified Data.Traversable as Traversable++import Control.Monad.State (State, runState, modify)++import Data.Maybe (mapMaybe, isJust, fromJust)++-- import qualified Data.Text as T++------------------------------------------------------------+-- Primitive RDF graph queries+------------------------------------------------------------++-- | 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 . getArcs++-- 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) (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 -> RDFGraph -> [[RDFVarBinding]]+rdfQueryBack qg tg =+ rdfQueryBack1 matchQueryVariable [] (getArcs qg) (getArcs 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 listProduct 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 qbs = listProduct $ map (vbmApply qbm . (:[])) qbs++------------------------------------------------------------+-- 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 <http://www.w3.org/TR/rdf-mt/>, 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 . getArcs++------------------------------------------------------------+-- 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 = 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 = (add emptyRDFGraph g,vs)+ where+ (g,vs) = runState ( Traversable.traverse (mapNode varb) gr ) []++-- 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 list of labels which accumulates all those+-- variables seen for which no substitution was available.+mapNode :: RDFVarBinding -> RDFLabel -> State [RDFLabel] RDFLabel+mapNode varb lab =+ case vbMap varb lab of+ Just v -> return v+ Nothing ->+ if isQueryVar lab then+ do { modify (addVar lab)+ ; return lab+ }+ else+ return lab++-- Add variable to list of variables, if not already there+addVar :: RDFLabel -> [RDFLabel] -> [RDFLabel]+addVar var vars = if var `elem` vars then vars else var:vars++------------------------------------------------------------+-- Simple lightweight query primitives+------------------------------------------------------------+--+-- [[[TODO: modify above code to use these for all graph queries]]]++-- |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 = 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 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/RDFRuleset.hs view
@@ -0,0 +1,546 @@+{-# LANGUAGE OverloadedStrings #-}++--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : RDFRuleset+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : 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.RDFRuleset+ ( 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.RDF.RDFQuery+ ( rdfQueryFind+ , rdfQueryBack, rdfQueryBackModify+ , rdfQuerySubs+ , rdfQuerySubsBlank+ )++import Swish.RDF.RDFGraph+ ( RDFLabel(..), RDFGraph+ , makeBlank, newNodes+ , merge, allLabels+ , toRDFGraph, emptyRDFGraph )++import Swish.RDF.RDFVarBinding (RDFVarBinding, RDFVarBindingModify)+import Swish.RDF.N3Parser (parseN3)+import Swish.RDF.Ruleset (Ruleset(..), RulesetMap)++import Swish.RDF.Rule+ ( Formula(..), Rule(..), RuleMap+ , fwdCheckInference+ , nullSN+ )++import Swish.RDF.VarBinding+ ( makeVarBinding+ , applyVarBinding, joinVarBindings+ , VarBindingModify(..)+ , vbmCompose+ , varBindingId+ )++import Swish.Utils.Namespace (Namespace, ScopedName, makeNSScopedName, namespaceToBuilder)+import Swish.RDF.Vocabulary (swishName, namespaceRDF, namespaceRDFS)++{-+import Swish.RDF.Proof+ ( Proof(..), Step(..) )+-}++import Swish.RDF.GraphClass (Label(..), Arc(..), LDGraph(..))+import Swish.Utils.ListHelpers (equiv, flist)++import Data.List (nub)+import Data.Maybe (fromMaybe)+import Data.Monoid (Monoid(..))++import qualified Data.Text as T+import qualified Data.Text.Lazy.Builder as B++------------------------------------------------------------+-- Datatypes for RDF ruleset+------------------------------------------------------------++type RDFFormula = Formula RDFGraph++type RDFRule = Rule RDFGraph++type RDFRuleMap = RuleMap RDFGraph++type RDFClosure = GraphClosure RDFLabel++type RDFRuleset = Ruleset RDFGraph++type RDFRulesetMap = RulesetMap RDFGraph++------------------------------------------------------------+-- Declare null RDF formula+------------------------------------------------------------++-- | The null RDF formula.+nullRDFFormula :: Formula RDFGraph+nullRDFFormula = Formula+ { formName = nullSN "nullRDFGraph"+ , formExpr = emptyRDFGraph+ }++------------------------------------------------------------+-- 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 :: [Arc lb] -- ^ Antecedent triples pattern+ -- (may include variable nodes)+ , ruleCon :: [Arc 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.+ }++instance (Label lb) => Eq (GraphClosure lb) where+ c1 == c2 = nameGraphRule c1 == nameGraphRule c2 &&+ ruleAnt c1 `equiv` ruleAnt c2 &&+ ruleCon c1 `equiv` ruleCon c2++instance (Label lb) => 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 emptyRDFGraph else foldl1 add 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 add 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 :: [Arc RDFLabel] -> RDFGraph -> [RDFVarBinding]+queryFind qas = rdfQueryFind (toRDFGraph qas)++queryBack :: [Arc RDFLabel] -> RDFGraph -> [[RDFVarBinding]]+queryBack qas = rdfQueryBack (toRDFGraph qas)++querySubs :: [RDFVarBinding] -> [Arc RDFLabel] -> [RDFGraph]+querySubs vars = rdfQuerySubs vars . toRDFGraph++querySubsBlank :: [RDFVarBinding] -> [Arc RDFLabel] -> [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+ -> T.Text -- ^ 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 = concatMap 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+ -> T.Text -- ^ 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+ -> T.Text -- ^ 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+ -> T.Text -- ^ 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+ -> T.Text -- ^ 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 (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 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/RDFVarBinding.hs view
@@ -0,0 +1,187 @@+{-# LANGUAGE OverloadedStrings #-}++--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : RDFVarBinding+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 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.RDFVarBinding+ ( RDFVarBinding, nullRDFVarBinding+ , RDFVarBindingModify, RDFOpenVarBindingModify, RDFOpenVarBindingModifyMap+ , RDFVarBindingFilter+ , rdfVarBindingUriRef, rdfVarBindingBlank+ , rdfVarBindingLiteral+ , rdfVarBindingUntypedLiteral, rdfVarBindingTypedLiteral+ , rdfVarBindingXMLLiteral, rdfVarBindingDatatyped+ , rdfVarBindingMemberProp+ )+where++import Swish.RDF.RDFGraph+ ( RDFLabel(..)+ , isLiteral, isUntypedLiteral, isTypedLiteral, isXMLLiteral+ , isDatatyped, isMemberProp, isUri, isBlank+ )++import Swish.RDF.VarBinding+ ( VarBinding(..), nullVarBinding+ , applyVarBinding + , VarBindingModify(..), OpenVarBindingModify+ , VarBindingFilter(..)+ , makeVarTestFilter+ )++import Swish.RDF.Vocabulary+ ( swishName )++import Swish.Utils.LookupMap+ ( LookupMap(..) )++------------------------------------------------------------+-- 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 = LookupMap 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 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/Rule.hs view
@@ -0,0 +1,247 @@+{-# LANGUAGE MultiParamTypeClasses, OverloadedStrings #-}++--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : Rule+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : MultiParamTypeClasses, OverloadedStrings+--+-- This module defines a framework for defining inference rules+-- 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.+--+--------------------------------------------------------------------------------++module Swish.RDF.Rule+ ( Expression(..), Formula(..), Rule(..), RuleMap+ , nullScope, nullSN, nullFormula, nullRule+ , fwdCheckInference, bwdCheckInference+ , showsFormula, showsFormulae, showsWidth+ )+ where++import Swish.Utils.Namespace (Namespace, makeNamespace, ScopedName, makeScopedName, makeNSScopedName)+import Swish.Utils.LookupMap (LookupEntryClass(..), LookupMap(..))+import Swish.Utils.ShowM (ShowM(..))++import Network.URI (URI, parseURI)+import Data.Maybe (fromJust)++import qualified Data.Text as T++------------------------------------------------------------+-- Expressions+------------------------------------------------------------++-- |Expression is a type class for values over which proofs+-- may be constructed.+class (Eq ex) => Expression ex where+ -- |Is expression true in all interpretations?+ -- If so, then its truth is assumed without justification.+ isValid :: ex -> Bool++------------------------------------------------------------+-- Formula: a named expression+------------------------------------------------------------++-- |A Formula is a named expression.+data Formula ex = Formula+ { formName :: ScopedName -- ^ Name used for formula in proof chain+ , formExpr :: ex -- ^ Named formula value+ } deriving Show++-- |Define equality of formulae as equality of formula names+instance Eq (Formula ex) where+ f1 == f2 = formName f1 == formName f2++-- |Define ordering of formulae based on formula names+instance Ord (Formula ex) where+ f1 <= f2 = formName f1 <= formName f2++instance LookupEntryClass (Formula ex) ScopedName (Formula ex)+ where+ newEntry (_,form) = form+ keyVal form = (formName form, form)++-- | The namespace @http:\/\/id.ninebynine.org\/2003\/Ruleset\/null@ with the prefix @null:@.+nullScope :: Namespace+nullScope = makeNamespace (Just "null") nullScopeURI++-- | Create a scoped name with the null namespace.+nullSN :: + T.Text -- ^ local name.+ -> ScopedName+nullSN = makeNSScopedName nullScope++tU :: String -> URI+tU = fromJust . parseURI++nullScopeURI :: URI+nullScopeURI = tU "http://id.ninebynine.org/2003/Ruleset/null"++-- | The null formula.+nullFormula :: Formula ex+nullFormula = Formula+ { formName = makeScopedName (Just "null") nullScopeURI "nullFormula"+ , formExpr = error "Null formula"+ }++-- testf1 = Formula "f1" ('f',1)+-- testf2 = Formula "f2" ('f',2)++-- |Return a displayable form of a list of labelled formulae+showsFormulae :: + (ShowM ex) + => String -- ^ newline+ -> [Formula ex] -- ^ the formulae to show+ -> String -- ^ text to be placed after the formulae+ -> ShowS+showsFormulae _ [] _ = id+showsFormulae newline [f] after = showsFormula newline f .+ showString after+showsFormulae newline (f:fs) after = showsFormula newline f .+ showString newline .+ showsFormulae newline fs after++-- |Create a displayable form of a labelled formula+showsFormula :: + (ShowM ex) + => String -- ^ newline+ -> Formula ex -- ^ formula+ -> ShowS+showsFormula newline f =+ showsWidth 16 ("["++show (formName f)++"] ") .+ showms (newline ++ replicate 16 ' ') (formExpr f)++------------------------------------------------------------+-- Rule+------------------------------------------------------------++-- |Rule is a data type for inference rules that can be used+-- to construct a step in a proof.+data Rule ex = Rule+ {+ -- |Name of rule, for use when displaying a proof+ ruleName :: ScopedName,+ + -- |Forward application of a rule, takes a list of+ -- expressions and returns a list (possibly empty)+ -- of forward applications of the rule to combinations+ -- of the antecedent expressions.+ -- Note that all of the results returned can be assumed to+ -- be (simultaneously) true, given the antecedents provided.+ fwdApply :: [ex] -> [ex],+ + -- |Backward application of a rule, takes an expression+ -- and returns a list of alternative antecedents, each of+ -- which is a list of expressions that jointly yield the+ -- given consequence through application of the inference+ -- rule. An empty list is returned if no antecedents+ -- will allow the consequence to be inferred.+ bwdApply :: ex -> [[ex]],+ + -- |Inference check. Takes a list of antecedent expressions+ -- and a consequent expression, returning True if the+ -- consequence can be obtained from the antecedents by+ -- application of the rule. When the antecedents and+ -- consequent are both given, this is generally more efficient+ -- that using either forward or backward chaining.+ -- Also, a particular rule may not fully support either+ -- forward or backward chaining, but all rules are required+ -- to fully support this function.+ --+ -- A default implementation based on forward chaining is+ -- given below.+ checkInference :: [ex] -> ex -> Bool + }++-- |Define equality of rules as equality of the rule names.+instance Eq (Rule ex) where+ r1 == r2 = ruleName r1 == ruleName r2++-- |Define ordering of rules based on the rule names.+instance Ord (Rule ex) where+ r1 <= r2 = ruleName r1 <= ruleName r2++instance Show (Rule ex) where+ show rl = "Rule "++show (ruleName rl)++instance LookupEntryClass (Rule ex) ScopedName (Rule ex)+ where+ newEntry (_,rule) = rule+ keyVal rule = (ruleName rule, rule)++type RuleMap ex = LookupMap (Rule ex)++-- | Checks that consequence is a result of+-- applying the rule to the antecedants.+fwdCheckInference :: + (Eq ex) + => Rule ex -- ^ rule+ -> [ex] -- ^ antecedants+ -> ex -- ^ consequence+ -> Bool+fwdCheckInference rule ante cons =+ cons `elem` fwdApply rule ante++bwdCheckInference ::+ (Eq ex) + => Rule ex -- ^ rule+ -> [ex] -- ^ antecedants+ -> ex -- ^ consequence+ -> Bool+bwdCheckInference rule ante cons = any checkAnts (bwdApply rule cons)+ where+ checkAnts = all (`elem` ante)++-- | The null rule.+nullRule :: Rule ex+nullRule = Rule+ { ruleName = makeScopedName (Just "null") nullScopeURI "nullRule"+ , fwdApply = \ _ -> []+ , bwdApply = \ _ -> []+ , checkInference = \ _ _ -> False+ }++------------------------------------------------------------+-- Shows formatting support functions+-----------------------------------------------------------++-- |Show a string left justified in a field of at least the specified+-- number of characters width.+showsWidth :: Int -> String -> ShowS+showsWidth wid str more = str++replicate pad ' '++more+ where+ pad = wid - length str+++--------------------------------------------------------------------------------+--+-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/Ruleset.hs view
@@ -0,0 +1,175 @@+{-# LANGUAGE MultiParamTypeClasses #-}+--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : Ruleset+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : MultiParamTypeClasses+--+-- This module defines a ruleset data type, used to collect information+-- about a ruleset that may contribute torwards inferences in RDF;+-- e.g. RDF and RDFS are rulesets.+--+-- A ruleset consists of a namespace, a collection of axioms, and+-- a collection of rules.+--+--------------------------------------------------------------------------------++module Swish.RDF.Ruleset+ ( Ruleset(..), RulesetMap+ , makeRuleset, getRulesetNamespace, getRulesetAxioms, getRulesetRules+ , getRulesetAxiom, getRulesetRule+ , getContextAxiom, getMaybeContextAxiom+ , getContextRule, getMaybeContextRule+ )+where++import Swish.Utils.Namespace (Namespace, ScopedName)+import Swish.RDF.Rule (Formula(..), Rule(..))++import Swish.Utils.LookupMap+ ( LookupEntryClass(..), LookupMap(..)+ , mapFindMaybe+ )++{-+Used for the Show instance of Ruleset, which was+used for debugging but has been removed as not+really needed by the general user.++import Swish.Utils.ShowM (ShowM(..))+import Data.List (intercalate)+-}++import Data.Maybe (fromMaybe, listToMaybe, mapMaybe)++-- | A Rule set.++data Ruleset ex = Ruleset+ { rsNamespace :: Namespace -- ^ Namespace.+ , rsAxioms :: [Formula ex] -- ^ Axioms.+ , rsRules :: [Rule ex] -- ^ Rules.+ }++{-++Used for debugging.++instance (ShowM ex) => Show (Ruleset ex) where+ show (Ruleset ns axs rls) = + intercalate "\n" + [ "Ruleset: " ++ show ns+ , "Axioms:" ]+ ++ (showsFormulae "\n" axs + (intercalate "\n" ("Rules:" : map show rls))) ""+-}++-- | Ruleset comparisons are based only on their namespace components.+instance Eq (Ruleset ex) where+ r1 == r2 = rsNamespace r1 == rsNamespace r2++instance LookupEntryClass (Ruleset ex) Namespace (Ruleset ex)+ where+ keyVal r@(Ruleset k _ _) = (k,r)+ newEntry (_,r) = r++type RulesetMap ex = LookupMap (Ruleset ex)++-- | Create a ruleset.+makeRuleset :: Namespace -> [Formula ex] -> [Rule ex] -> Ruleset ex+makeRuleset nsp fms rls = Ruleset+ { rsNamespace = nsp+ , rsAxioms = fms+ , rsRules = rls+ }++-- | Extract the namespace of a ruleset.+getRulesetNamespace :: Ruleset ex -> Namespace+getRulesetNamespace = rsNamespace++-- | Extract the axioms from a ruleset.+getRulesetAxioms :: Ruleset ex -> [Formula ex]+getRulesetAxioms = rsAxioms++-- | Extract the rules from a ruleset.+getRulesetRules :: Ruleset ex -> [Rule ex]+getRulesetRules = rsRules++-- | Find a named axiom in a ruleset.+getRulesetAxiom :: ScopedName -> Ruleset ex -> Maybe (Formula ex)+getRulesetAxiom nam rset =+ mapFindMaybe nam (LookupMap (getRulesetAxioms rset))+ -- listToMaybe $ filter ( (matchName nam) . formName ) $ getRulesetAxioms rset++-- | Find a named rule in a ruleset. +getRulesetRule :: ScopedName -> Ruleset ex -> Maybe (Rule ex)+getRulesetRule nam rset =+ mapFindMaybe nam (LookupMap (getRulesetRules rset))+ -- listToMaybe $ filter ( (matchName nam) . ruleName ) $ getRulesetRules rset++-- | Find a named axiom in a proof context.+getContextAxiom :: + ScopedName -- ^ Name of axiom.+ -> Formula ex -- ^ Default axiom (used if named component does not exist).+ -> [Ruleset ex] -- ^ Rulesets to search.+ -> Formula ex+getContextAxiom nam def rsets = fromMaybe def (getMaybeContextAxiom nam rsets)+ {-+ foldr (flip fromMaybe) def $ map (getRulesetAxiom nam) rsets+ -}++-- | Find a named axiom in a proof context.+getMaybeContextAxiom ::+ ScopedName -- ^ Name of axiom.+ -> [Ruleset ex] -- ^ Rulesets to search.+ -> Maybe (Formula ex)+getMaybeContextAxiom nam rsets =+ listToMaybe $ mapMaybe (getRulesetAxiom nam) rsets++-- | Find a named rule in a proof context.+getContextRule :: + ScopedName -- ^ Name of rule.+ -> Rule ex -- ^ Default rule (used if named component does not exist).+ -> [Ruleset ex] -- ^ Rulesets to search.+ -> Rule ex+getContextRule nam def rsets = fromMaybe def (getMaybeContextRule nam rsets)+ {-+ foldr (flip fromMaybe) def $ map (getRulesetRule nam) rsets+ -}++-- | Find a named rule in a proof context.+getMaybeContextRule :: + ScopedName -- ^ Name of rule.+ -> [Ruleset ex] -- ^ Rulesets to search.+ -> Maybe (Rule ex)+getMaybeContextRule nam rsets =+ listToMaybe $ mapMaybe (getRulesetRule nam) rsets++--------------------------------------------------------------------------------+--+-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/SwishCommands.hs view
@@ -0,0 +1,422 @@+{-# LANGUAGE OverloadedStrings #-}++--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : SwishCommands+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : OverloadedStrings+--+-- SwishCommands: functions to deal with indivudual Swish command options.+--+--------------------------------------------------------------------------------++module Swish.RDF.SwishCommands+ ( swishFormat+ , swishBase+ -- , swishVerbose+ , swishInput+ , swishOutput+ , swishMerge+ , swishCompare+ , swishGraphDiff+ , swishScript+ )+where++import Swish.RDF.SwishMonad+ ( SwishStateIO, SwishState(..), SwishStatus(..)+ , setFormat, setBase, setGraph+ , resetInfo, resetError, setStatus+ -- , setVerbose+ , SwishFormat(..)+ , swishError+ , reportLine+ )++import Swish.RDF.SwishScript (parseScriptFromText)++import Swish.RDF.GraphPartition+ ( GraphPartition(..)+ , partitionGraph, comparePartitions+ , partitionShowP+ )++import Swish.RDF.RDFGraph+ ( RDFGraph, merge )++import qualified Swish.RDF.TurtleFormatter as TTLF+import qualified Swish.RDF.N3Formatter as N3F+import qualified Swish.RDF.NTFormatter as NTF++import Swish.RDF.TurtleParser (parseTurtle)+import Swish.RDF.N3Parser (parseN3)+import Swish.RDF.NTParser (parseNT)+import Swish.RDF.RDFParser (appendURIs)++import Swish.RDF.GraphClass+ ( LDGraph(..)+ , Label(..)+ )++import Swish.Utils.QName (QName, qnameFromURI, qnameFromFilePath, getQNameURI)++import System.IO+ ( Handle, openFile, IOMode(..)+ , hPutStr, hPutStrLn, hClose+ , hIsReadable, hIsWritable+ , stdin, stdout+ )++import Network.URI (parseURIReference)++import Control.Monad.Trans (MonadTrans(..))+import Control.Monad.State (modify, gets)+import Control.Monad (liftM, when)++import qualified Data.Text.Lazy as T+import qualified Data.Text.Lazy.IO as IO+import System.IO.Error++import Data.Maybe (isJust, fromMaybe)++------------------------------------------------------------+-- Set file format to supplied value+------------------------------------------------------------++-- the second argument allows for options to be passed along+-- with the format (a la cwm) but this is not supported yet+--+swishFormat :: SwishFormat -> Maybe String -> SwishStateIO ()+swishFormat fmt _ = modify (setFormat fmt)++------------------------------------------------------------+-- Set base URI to supplied value+------------------------------------------------------------++-- the Maybe String argument is ignored (a result of a lack of+-- design with the command-line processing)+--+swishBase :: Maybe QName -> Maybe String -> SwishStateIO ()+swishBase mb _ = modify (setBase mb)++------------------------------------------------------------+-- Read graph from named file+------------------------------------------------------------++swishInput :: Maybe String -> SwishStateIO ()+swishInput fnam =+ swishReadGraph fnam >>= maybe (return ()) (modify . setGraph)+ +------------------------------------------------------------+-- Merge graph from named file+------------------------------------------------------------++swishMerge :: Maybe String -> 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)++------------------------------------------------------------+-- Compare graph from named file+------------------------------------------------------------++swishCompare :: Maybe String -> 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+------------------------------------------------------------++swishGraphDiff :: Maybe String -> SwishStateIO ()+swishGraphDiff fnam =+ swishReadGraph fnam >>= maybe (return ()) diffGraph++diffGraph :: RDFGraph -> SwishStateIO ()+diffGraph gr = do+ oldGr <- gets graph+ let p1 = partitionGraph (getArcs oldGr)+ p2 = partitionGraph (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+------------------------------------------------------------++swishScript :: Maybe String -> 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 = fromMaybe defURI `liftM` gets 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 $ 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 ()++------------------------------------------------------------+-- Output graph to named file+------------------------------------------------------------++swishOutput :: Maybe String -> 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 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 <- lift $ try $ openFile 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 <- lift $ try $ openFile 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 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/SwishMain.hs view
@@ -0,0 +1,291 @@+--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : SwishMain+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : H98+--+-- 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.RDF.SwishScript")+-- 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.+--+--------------------------------------------------------------------------------++-- TODO:+--+-- * Add RDF/XML input and output+--++module Swish.RDF.SwishMain (+ SwishStatus(..), SwishAction,+ runSwish,+ runSwishActions,+ displaySwishHelp,+ splitArguments,+ validateCommands+ ) where++import Swish.RDF.SwishCommands+ ( swishFormat+ , swishBase+ , swishInput+ , swishOutput+ , swishMerge+ , swishCompare+ , swishGraphDiff+ , swishScript+ )++import Swish.RDF.SwishMonad+ ( SwishStateIO, SwishState(..), SwishStatus(..)+ , emptyState+ , SwishFormat(..)+ )++import Swish.Utils.QName (qnameFromURI)+import Swish.Utils.ListHelpers (breakAll)++import Control.Monad.State (execStateT)+import Control.Monad (liftM)++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)+ in case nam of+ "-ttl" -> wrap $ swishFormat Turtle+ "-nt" -> wrap $ swishFormat NT+ "-n3" -> wrap $ 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 Nothing)+validateBase arg (Just b) =+ case fmap qnameFromURI (parseURI b) of+ j@(Just _) -> Right $ SA (arg, swishBase j Nothing)+ _ -> 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++-- | Execute the given set of actions.+runSwishActions :: [SwishAction] -> IO SwishStatus+runSwishActions acts = exitcode `liftM` execStateT (swishCommands acts) emptyState++--------------------------------------------------------------------------------+--+-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/SwishMonad.hs view
@@ -0,0 +1,270 @@+{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-}+--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : SwishMonad+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : FlexibleInstances, MultiParamTypeClasses+--+-- Composed state and IO monad for Swish+--+--------------------------------------------------------------------------------++module Swish.RDF.SwishMonad+ ( SwishStateIO, SwishState(..), SwishStatus(..)+ , setFormat, setBase, setGraph+ , modGraphs, findGraph, findFormula+ , modRules, findRule+ , modRulesets, findRuleset+ , findOpenVarModify, findDatatype+ , setInfo, resetInfo, setError, resetError+ , setStatus+ -- , setVerbose+ , emptyState+ , SwishFormat(..)+ , NamedGraph(..), NamedGraphMap+ , swishError+ , reportLines, reportLine+ )+where++import Swish.RDF.RDFGraph+ ( RDFGraph, emptyRDFGraph )++import Swish.RDF.RDFRuleset+ ( RDFFormula, RDFRule, RDFRuleMap, RDFRuleset, RDFRulesetMap )++import Swish.RDF.RDFDatatype+ ( RDFDatatype )++import Swish.RDF.RDFVarBinding+ ( RDFOpenVarBindingModify+ )++import Swish.RDF.BuiltInMap+ ( findRDFOpenVarBindingModifier+ , findRDFDatatype+ , rdfRulesetMap+ )++import Swish.RDF.Ruleset+ ( getMaybeContextAxiom+ , getMaybeContextRule+ )++import Swish.RDF.Rule+ ( Formula(..)+ )++import Swish.Utils.Namespace (ScopedName, getScopeNamespace)+import Swish.Utils.QName (QName)++import Swish.Utils.LookupMap+ ( LookupEntryClass(..), LookupMap(..)+ , emptyLookupMap+ , mapFindMaybe+ , mapVals+ )++import Control.Monad.Trans (MonadTrans(..))+import Control.Monad.State (StateT(..), modify)++import System.IO (hPutStrLn, stderr)++{-|+The supported input and output formats.+-}+data SwishFormat = + Turtle -- ^ Turtle format+ | N3 -- ^ N3 format+ | NT -- ^ NTriples format+ deriving 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 message+ }++-- | 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 (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."++type SwishStateIO a = StateT SwishState IO a++emptyState :: SwishState+emptyState = SwishState+ { format = N3+ , base = Nothing+ , graph = emptyRDFGraph+ , graphs = emptyLookupMap+ , rules = emptyLookupMap+ , rulesets = rdfRulesetMap+ , infomsg = Nothing+ , errormsg = Nothing+ , exitcode = SwishSuccess+ }++setFormat :: SwishFormat -> SwishState -> SwishState+setFormat fm state = state { format = fm }++setBase :: Maybe QName -> SwishState -> SwishState+setBase bs state = state { base = bs }++setGraph :: RDFGraph -> SwishState -> SwishState+setGraph gr state = state { graph = gr }++modGraphs ::+ ( NamedGraphMap -> NamedGraphMap ) -> SwishState -> SwishState+modGraphs grmod state = state { graphs = grmod (graphs state) }++findGraph :: ScopedName -> SwishState -> Maybe [RDFGraph]+findGraph nam state = mapFindMaybe nam (graphs state)++findFormula :: ScopedName -> SwishState -> Maybe RDFFormula+findFormula nam state = case findGraph nam state of+ Nothing -> getMaybeContextAxiom nam (mapVals $ rulesets state)+ Just [] -> Just $ Formula nam emptyRDFGraph+ Just grs -> Just $ Formula nam (head grs)++modRules ::+ ( RDFRuleMap -> RDFRuleMap ) -> SwishState -> SwishState+modRules rlmod state = state { rules = rlmod (rules state) }++findRule :: ScopedName -> SwishState -> Maybe RDFRule+findRule nam state =+ let+ localrule = mapFindMaybe nam (rules state)+ contextrule = getMaybeContextRule nam $ mapVals $ rulesets state+ in+ case localrule of+ Nothing -> contextrule+ justlr -> justlr++modRulesets ::+ ( RDFRulesetMap -> RDFRulesetMap ) -> SwishState -> SwishState+modRulesets rsmod state = state { rulesets = rsmod (rulesets state) }++findRuleset ::+ ScopedName -> SwishState -> Maybe RDFRuleset+findRuleset nam state = mapFindMaybe (getScopeNamespace nam) (rulesets state)++findOpenVarModify :: ScopedName -> SwishState -> Maybe RDFOpenVarBindingModify+findOpenVarModify nam _ = findRDFOpenVarBindingModifier nam++findDatatype :: ScopedName -> SwishState -> Maybe RDFDatatype+findDatatype nam _ = findRDFDatatype nam++setInfo :: String -> SwishState -> SwishState+setInfo msg state = state { infomsg = Just msg }++resetInfo :: SwishState -> SwishState+resetInfo state = state { infomsg = Nothing }++setError :: String -> SwishState -> SwishState+setError msg state = state { errormsg = Just msg }++resetError :: SwishState -> SwishState+resetError state = state { errormsg = Nothing }++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]+ }++instance LookupEntryClass NamedGraph ScopedName [RDFGraph]+ where+ keyVal (NamedGraph k v) = (k,v)+ newEntry (k,v) = NamedGraph k v++type NamedGraphMap = LookupMap NamedGraph++-- | Report error and set exit status code++swishError :: String -> SwishStatus -> SwishStateIO ()+swishError msg sts = do+ reportLines [msg, show sts ++ "\n"]+ -- when (sts == 4) $ reportLine "Use 'Swish -h' or 'Swish -?' for help\n"+ modify $ setStatus sts++-- | Output text to the standard error stream+--+-- Each string in the supplied list is a line of text to+-- be displayed.++reportLines :: [String] -> SwishStateIO ()+reportLines = mapM_ reportLine ++reportLine :: String -> SwishStateIO ()+reportLine line =+ -- lift putStrLn line+ lift $ hPutStrLn stderr line++--------------------------------------------------------------------------------+--+-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/SwishScript.hs view
@@ -0,0 +1,1512 @@+{-# LANGUAGE OverloadedStrings #-}+--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+{- |+Module : SwishScript+Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+License : GPL V2++Maintainer : Douglas Burke+Stability : experimental+Portability : OverloadedStrings++This module implements the Swish script processor: it parses a script+from a supplied string, and returns a list of Swish state transformer+functions whose effect, when applied to a state value, is to implement+the supplied script.++-}++module Swish.RDF.SwishScript+ ( + -- * Syntax+ -- $syntax+ + -- ** Defining a prefix+ -- $prefixLine+ + -- ** Naming a graph+ -- $nameItem+ + -- ** Reading and writing graphs+ + -- $readGraph+ + -- $writeGraph+ + -- ** Merging graphs+ -- $mergeGraphs+ + -- ** Comparing graphs+ + -- $compareGraphs+ + -- $assertEquiv+ + -- $assertMember+ + -- ** Defining rules+ + -- $defineRule+ + -- $defineRuleset+ + -- $defineConstraints+ + -- ** Apply a rule+ -- $fwdChain+ + -- $bwdChain+ + -- ** Define a proof+ -- $proof+ + -- * An example script+ -- $exampleScript+ + -- * Parsing+ + parseScriptFromText + )+where++import Swish.RDF.SwishMonad+ ( SwishStateIO, SwishStatus(..) + , modGraphs, findGraph, findFormula+ , modRules, findRule+ , modRulesets, findRuleset+ , findOpenVarModify, findDatatype+ , setInfo, setError, setStatus+ , NamedGraph(..)+ )++import Swish.RDF.RDFDatatype+ ( RDFDatatype )++import Swish.RDF.RDFRuleset+ ( RDFFormula, RDFRule+ , RDFRuleset+ , makeRDFClosureRule+ )++import Swish.RDF.RDFProof+ ( RDFProofStep, makeRDFProof, makeRDFProofStep )++import Swish.RDF.RDFVarBinding+ ( RDFVarBindingModify+ )++import Swish.RDF.RDFGraphShowM()++import Swish.RDF.RDFGraph+ ( RDFGraph, RDFLabel(..)+ , emptyRDFGraph+ , NamespaceMap+ , setNamespaces+ , merge, add+ )++import Swish.RDF.RDFParser (whiteSpace, lexeme, symbol, eoln, manyTill)++import Swish.RDF.N3Parser+ ( parseAnyfromText+ , parseN3 + , N3Parser, N3State(..)+ , getPrefix+ , subgraph+ , n3symbol -- was uriRef2,+ , quickVariable -- was varid+ , lexUriRef+ , newBlankNode+ )++import Swish.RDF.N3Formatter (formatGraphAsBuilder)+import Swish.RDF.Datatype (typeMkRules)+import Swish.RDF.Proof (explainProof, showsProof)+import Swish.RDF.Ruleset (makeRuleset, getRulesetRule, getMaybeContextRule)+import Swish.RDF.Rule (Formula(..), Rule(..)) +import Swish.RDF.VarBinding (composeSequence)++import Swish.Utils.Namespace (ScopedName, getScopeNamespace)+import Swish.Utils.QName (QName, qnameFromURI)+import Swish.Utils.LookupMap (mapReplaceOrAdd)+import Swish.Utils.ListHelpers (equiv, flist)++import qualified Data.Text.Lazy as L+import qualified Data.Text.Lazy.Builder as B+import qualified Data.Text.Lazy.IO as LIO+import Text.ParserCombinators.Poly.StateText++import Control.Monad (unless, when, liftM)+import Control.Monad.State (modify, gets, lift)++import Network.URI (URI(..))++import Data.Monoid (Monoid(..))++import qualified System.IO.Error as IO++------------------------------------------------------------+--+-- The parser used to be based on the Notation3 parser, and used many+-- of the same syntax productions, but the top-level productions used+-- are quite different. With the parser re-write it's less clear+-- what is going on.+--+-- NOTE: during the parser re-write we strip out some of this functionality+-- ++-- | Parser for Swish script processor+parseScriptFromText :: + Maybe QName -- ^ Default base for the script+ -> L.Text -- ^ Swish script+ -> Either String [SwishStateIO ()]+parseScriptFromText = parseAnyfromText script ++----------------------------------------------------------------------+-- Syntax productions+----------------------------------------------------------------------++between :: Parser s lbr -> Parser s rbr -> Parser s a -> Parser s a+between = bracket++n3SymLex :: N3Parser ScopedName+n3SymLex = lexeme n3symbol++setTo :: N3Parser ()+setTo = isymbol ":-"++semicolon :: N3Parser ()+semicolon = isymbol ";"++comma :: N3Parser ()+comma = isymbol ","++commentText :: N3Parser String+commentText = semicolon *> restOfLine++script :: N3Parser [SwishStateIO ()]+script = do+ whiteSpace+ scs <- many command+ eof+ return scs++isymbol :: String -> N3Parser ()+isymbol s = symbol s >> return ()++command :: N3Parser (SwishStateIO ())+command =+ prefixLine+ <|> nameItem+ <|> readGraph+ <|> writeGraph+ <|> mergeGraphs+ <|> compareGraphs+ <|> assertEquiv+ <|> assertMember+ <|> defineRule+ <|> defineRuleset+ <|> defineConstraints+ <|> checkProofCmd+ <|> fwdChain+ <|> bwdChain++prefixLine :: N3Parser (SwishStateIO ())+prefixLine = do+ -- try $ isymbol "@prefix"+ isymbol "@prefix"+ getPrefix+ whiteSpace+ isymbol "."+ return $ return ()++-- name :- graph+-- name :- ( graph* )+nameItem :: N3Parser (SwishStateIO ())+nameItem = + ssAddGraph <$> n3SymLex <*> (symbol ":-" *> graphOrList)+ +maybeURI :: N3Parser (Maybe URI)+maybeURI = (Just <$> lexUriRef) <|> return Nothing++-- @read name [ <uri> ]+readGraph :: N3Parser (SwishStateIO ())+readGraph = commandName "@read" *> (ssRead <$> n3SymLex <*> maybeURI)++-- @write name [ <uri> ] ; Comment+writeGraph :: N3Parser (SwishStateIO ())+writeGraph =+ do { commandName "@write"+ ; n <- n3SymLex+ ; let gs = ssGetList n :: SwishStateIO (Either String [RDFGraph])+ ; muri <- maybeURI+ ; c <- commentText+ ; return $ ssWriteList muri gs c+ }++-- @merge ( name* ) => name+mergeGraphs :: N3Parser (SwishStateIO ())+mergeGraphs = do+ commandName "@merge"+ gs <- graphList+ isymbol "=>"+ n <- n3SymLex+ return $ ssMerge n gs++-- @compare name name+compareGraphs :: N3Parser (SwishStateIO ())+compareGraphs =+ commandName "@compare" *> (ssCompare <$> n3SymLex <*> n3SymLex)+ +-- @<command> name name ; Comment+assertArgs :: (ScopedName -> ScopedName -> String -> SwishStateIO ())+ -> String -> N3Parser (SwishStateIO ())+assertArgs assertFunc cName = do+ commandName $ '@':cName+ assertFunc <$> n3SymLex <*> n3SymLex <*> commentText+ +-- @asserteq name name ; Comment+assertEquiv :: N3Parser (SwishStateIO ())+assertEquiv = assertArgs ssAssertEq "asserteq" + +-- @assertin name name ; Comment +assertMember :: N3Parser (SwishStateIO ())+assertMember = assertArgs ssAssertIn "assertin"+ +-- @rule name :- ( name* ) => name [ | ( (name var*)* ) ] +defineRule :: N3Parser (SwishStateIO ())+defineRule =+ do { commandName "@rule"+ ; rn <- n3SymLex+ ; setTo+ ; ags <- graphOrList+ ; isymbol "=>"+ ; cg <- graphExpr+ ; vms <- varModifiers <|> pure []+ ; return $ ssDefineRule rn ags cg vms+ }++-- @ruleset name :- ( name* ) ; ( name* )+defineRuleset :: N3Parser (SwishStateIO ())+defineRuleset =+ commandName "@ruleset" *> + (ssDefineRuleset <$> n3SymLex <*> (setTo *> nameList) <*> (semicolon *> nameList))+ +-- @constraints pref :- ( name* ) | ( name* )+defineConstraints :: N3Parser (SwishStateIO ())+defineConstraints =+ commandName "@constraints" *> + (ssDefineConstraints <$> n3SymLex <*> (setTo *> graphOrList) <*> (symbol "|" *> nameOrList))+ +-- @proof name ( name* )+-- @input name+-- @step name ( name* ) => name # rule-name, antecedents, consequent+-- @result name+checkProofCmd :: N3Parser (SwishStateIO ())+checkProofCmd =+ do { commandName "@proof"+ ; pn <- n3SymLex+ ; sns <- nameList+ ; commandName "@input"+ ; igf <- formulaExpr+ ; sts <- many checkStep+ ; commandName "@result"+ ; rgf <- formulaExpr+ ; return $ ssCheckProof pn sns igf sts rgf+ }++checkStep ::+ N3Parser (Either String [RDFRuleset]+ -> SwishStateIO (Either String RDFProofStep))+checkStep =+ commandName "@step" *> + (ssCheckStep <$> n3SymLex <*> formulaList <*> (symbol "=>" *> formulaExpr))++-- # ruleset rule (antecedents) => result+-- @fwdchain pref name ( name* ) => name+fwdChain :: N3Parser (SwishStateIO ())+fwdChain =+ do { commandName "@fwdchain"+ ; sn <- n3SymLex+ ; rn <- n3SymLex+ ; ags <- graphOrList+ ; isymbol "=>"+ ; cn <- n3SymLex+ ; s <- stGet+ ; let prefs = prefixUris s+ ; return $ ssFwdChain sn rn ags cn prefs+ }++-- # ruleset rule consequent <= (antecedent-alts)+-- @bwdchain pref name graph <= name+bwdChain :: N3Parser (SwishStateIO ())+bwdChain =+ do { commandName "@bwdchain"+ ; sn <- n3SymLex+ ; rn <- n3SymLex+ ; cg <- graphExpr+ ; isymbol "<="+ ; an <- n3SymLex+ ; s <- stGet+ ; let prefs = prefixUris s+ ; return $ ssBwdChain sn rn cg an prefs+ }++----------------------------------------------------------------------+-- Syntax clause helpers+----------------------------------------------------------------------++-- TODO: is the loss of identLetter a problem?+commandName :: String -> N3Parser ()+-- commandName cmd = try (string cmd *> notFollowedBy identLetter *> whiteSpace)+commandName cmd = symbol cmd *> pure ()++restOfLine :: N3Parser String+restOfLine = manyTill (satisfy (const True)) eoln <* whiteSpace+ +br :: N3Parser a -> N3Parser a+br = between (symbol "(") (symbol ")")++nameList :: N3Parser [ScopedName]+nameList = br $ many n3SymLex+ +toList :: a -> [a]+toList = (:[])+ +nameOrList :: N3Parser [ScopedName]+nameOrList =+ (toList <$> n3SymLex) + <|> nameList+ +graphExpr :: N3Parser (SwishStateIO (Either String RDFGraph))+graphExpr =+ graphOnly+ <|>+ do { f <- formulaExpr+ ; return $ liftM (liftM formExpr) f+ }++graphOnly :: N3Parser (SwishStateIO (Either String RDFGraph))+graphOnly =+ do { isymbol "{"+ ; b <- newBlankNode+ ; g <- subgraph b+ ; isymbol "}"+ ; s <- stGet+ ; let gp = setNamespaces (prefixUris s) g+ ; return $ return (Right gp)+ }++graphList :: N3Parser [SwishStateIO (Either String RDFGraph)]+graphList = br (many graphExpr)++graphOrList :: N3Parser [SwishStateIO (Either String RDFGraph)]+graphOrList =+ (toList <$> graphExpr)+ <|> graphList++formulaExpr :: N3Parser (SwishStateIO (Either String RDFFormula))+formulaExpr = n3SymLex >>= namedGraph++namedGraph :: ScopedName -> N3Parser (SwishStateIO (Either String RDFFormula))+namedGraph n =+ (ssAddReturnFormula n <$> (setTo *> graphOnly))+ <|> return (ssGetFormula n)++formulaList :: N3Parser [SwishStateIO (Either String RDFFormula)]+formulaList = between (symbol "(") (symbol ")") (many formulaExpr)++varModifiers :: N3Parser [(ScopedName,[RDFLabel])]+varModifiers = symbol "|" *> varModList++varModList :: N3Parser [(ScopedName,[RDFLabel])]+varModList = + br (sepBy varMod comma)+ <|> toList <$> lexeme varMod++varMod :: N3Parser (ScopedName,[RDFLabel])+varMod = (,) <$> n3SymLex <*> many (lexeme quickVariable)++----------------------------------------------------------------------+-- SwishState helper functions+----------------------------------------------------------------------+--+-- The functions below operate in the SwishStateIO monad, and are used+-- to assemble an executable version of the parsed script.++-- | Return a message to the user. At present the message begins with '# '+-- but this may be removed.+--+ssReport :: + String -- ^ message contents+ -> SwishStateIO ()+-- ssReport msg = lift $ putStrLn $ "# " ++ msg+ssReport msg = modify $ setInfo $ "# " ++ msg++ssReportLabel :: + String -- ^ label for the message+ -> String -- ^ message contents+ -> SwishStateIO ()+ssReportLabel lbl msg = ssReport $ lbl ++ ": " ++ msg++ssAddReturnFormula ::+ ScopedName -> SwishStateIO (Either String RDFGraph)+ -> SwishStateIO (Either String RDFFormula)+ssAddReturnFormula nam gf =+ do { egr <- gf+ ; ssAddGraph nam [return egr]+ ; return $ liftM (Formula nam) egr+ }++ssAddGraph ::+ ScopedName -> [SwishStateIO (Either String RDFGraph)]+ -> SwishStateIO ()+ssAddGraph nam gf =+ let errmsg = "Graph/list not added: "++show nam++"; "+ in+ do { esg <- sequence gf -- [Either String RDFGraph]+ ; let egs = sequence esg -- Either String [RDFGraph]+ ; let fgs = case egs of+ Left er -> setError (errmsg++er)+ Right gs -> modGraphs (mapReplaceOrAdd (NamedGraph nam gs))+ ; modify fgs+ }++ssGetGraph :: ScopedName -> SwishStateIO (Either String RDFGraph)+ssGetGraph nam = liftM head <$> ssGetList nam+ +ssGetFormula :: ScopedName -> SwishStateIO (Either String RDFFormula)+ssGetFormula nam = gets find+ where+ find st = case findFormula nam st of+ Nothing -> Left ("Formula not present: "++show nam)+ Just gr -> Right gr++ssGetList :: ScopedName -> SwishStateIO (Either String [RDFGraph])+ssGetList nam = gets find+ where+ find st = case findGraph nam st of+ Nothing -> Left ("Graph or list not present: "++show nam)+ Just grs -> Right grs++ssRead :: ScopedName -> Maybe URI -> SwishStateIO ()+ssRead nam muri = ssAddGraph nam [ssReadGraph muri]++ssReadGraph :: Maybe URI -> SwishStateIO (Either String RDFGraph)+ssReadGraph muri = + let gf inp = case inp of+ Left es -> Left es+ Right is -> parseN3 is (fmap qnameFromURI muri)+ + in gf `liftM` getResourceData muri++ssWriteList ::+ Maybe URI -> SwishStateIO (Either String [RDFGraph]) -> String+ -> SwishStateIO ()+ssWriteList muri gf comment = do+ esgs <- gf+ case esgs of+ Left er -> modify $ setError ("Cannot write list: "++er)+ Right [] -> putResourceData Nothing (B.fromLazyText (L.concat ["# ", L.pack comment, "\n+ Swish: Writing empty list"]))+ Right [gr] -> ssWriteGraph muri gr comment+ Right grs -> mapM_ writegr (zip [(0::Int)..] grs)+ where+ writegr (n,gr) = ssWriteGraph (murin muri n) gr+ ("["++show n++"] "++comment)+ murin Nothing _ = Nothing+ murin (Just uri) n = + let rp = reverse $ uriPath uri+ (rLastSet, rRest) = break (=='/') rp+ (before, after) = break (=='.') $ reverse rLastSet+ newPath = reverse rRest ++ "/" ++ before ++ show n ++ after+ in case rLastSet of+ "" -> error $ "Invalid URI (path ends in /): " ++ show uri+ _ -> Just $ uri { uriPath = newPath }+ + ++{-+ssWrite ::+ Maybe String -> SwishStateIO (Either String RDFGraph) -> String+ -> SwishStateIO ()+ssWrite muri gf comment =+ do { esg <- gf+ ; case esg of+ Left er -> modify $ setError ("Cannot write graph: "++er)+ Right gr -> ssWriteGraph muri gr comment+ }+-}++ssWriteGraph :: Maybe URI -> RDFGraph -> String -> SwishStateIO ()+ssWriteGraph muri gr comment =+ putResourceData muri (c `mappend` formatGraphAsBuilder gr)+ where+ c = B.fromLazyText $ L.concat ["# ", L.pack comment, "\n"]++ssMerge ::+ ScopedName -> [SwishStateIO (Either String RDFGraph)]+ -> SwishStateIO ()+ssMerge nam gfs =+ let errmsg = "Graph merge not defined: "++show nam++"; "+ in+ do { ssReportLabel "Merge" (show nam)+ ; esg <- sequence gfs -- [Either String RDFGraph]+ ; let egs = sequence esg -- Either String [RDFGraph]+ ; let fgs = case egs of+ Left er -> setError (errmsg++er)+ Right [] -> setError (errmsg++"No graphs to merge")+ Right gs -> modGraphs (mapReplaceOrAdd (NamedGraph nam [g]))+ where g = foldl1 merge gs+ ; modify fgs+ }++ssCompare :: ScopedName -> ScopedName -> SwishStateIO ()+ssCompare n1 n2 =+ do { ssReportLabel "Compare" (show n1 ++ " " ++ show n2)+ ; g1 <- ssGetGraph n1+ ; g2 <- ssGetGraph n2+ ; when (g1 /= g2) (modify $ setStatus SwishGraphCompareError)+ }++ssAssertEq :: ScopedName -> ScopedName -> String -> SwishStateIO ()+ssAssertEq n1 n2 comment =+ let er1 = ":\n Graph or list compare not performed: invalid graph/list."+ in+ do { ssReportLabel "AssertEq" comment+ ; g1 <- ssGetList n1+ ; g2 <- ssGetList n2+ ; case (g1,g2) of+ (Left er,_) -> modify $ setError (comment++er1++"\n "++er)+ (_,Left er) -> modify $ setError (comment++er1++"\n "++er)+ (Right gr1,Right gr2) -> + unless (equiv gr1 gr2) $ modify $+ setError (comment++":\n Graph "++show n1+ ++" differs from "++show n2++".")+ }++ssAssertIn :: ScopedName -> ScopedName -> String -> SwishStateIO ()+ssAssertIn n1 n2 comment =+ let er1 = ":\n Membership test not performed: invalid graph."+ er2 = ":\n Membership test not performed: invalid list."+ in+ do { ssReportLabel "AssertIn" comment+ ; g1 <- ssGetGraph n1+ ; g2 <- ssGetList n2+ ; case (g1,g2) of+ (Left er,_) -> modify $ setError (comment++er1++"\n "++er)+ (_,Left er) -> modify $ setError (comment++er2++"\n "++er)+ (Right gr,Right gs) ->+ unless (gr `elem` gs) $ modify $+ setError (comment++":\n Graph "++show n1+ ++" not a member of "++show n2)+ }++-- Note: this is probably incomplete, though it should work in simple cases.+-- A complete solution would have the binding modifiers subject to+-- re-arrangement to suit the actual bound variables encountered.+-- See VarBinding.findCompositions and VarBinding.findComposition+--+-- This code should be adequate if variable bindings are always used+-- in combinations consisting of a single modifier followed by any number+-- of filters.+--+ssDefineRule ::+ ScopedName+ -> [SwishStateIO (Either String RDFGraph)]+ -> SwishStateIO (Either String RDFGraph)+ -> [(ScopedName,[RDFLabel])]+ -> SwishStateIO ()+ssDefineRule rn agfs cgf vmds =+ let errmsg1 = "Rule definition error in antecedent graph(s): "+ errmsg2 = "Rule definition error in consequent graph: "+ errmsg3 = "Rule definition error in variable modifier(s): "+ errmsg4 = "Incompatible variable binding modifier sequence"+ in+ do { aesg <- sequence agfs -- [Either String RDFGraph]+ ; let ags = sequence aesg :: Either String [RDFGraph]+ ; cg <- cgf -- Either String RDFGraph+ ; let vmfs = map ssFindVarModify vmds+ ; evms <- sequence vmfs -- [Either String RDFVarBindingModify]+ ; let vms = sequence evms :: Either String [RDFVarBindingModify]+ ; let frl = case (ags,cg,vms) of+ (Left er,_,_) -> setError (errmsg1++er)+ (_,Left er,_) -> setError (errmsg2++er)+ (_,_,Left er) -> setError (errmsg3++er)+ (Right agrs,Right cgr,Right vbms) ->+ let+ newRule = makeRDFClosureRule rn agrs cgr+ in+ case composeSequence vbms of+ Just vm -> modRules (mapReplaceOrAdd (newRule vm))+ Nothing -> setError errmsg4+ ; modify frl+ }++ssFindVarModify ::+ (ScopedName,[RDFLabel]) -> SwishStateIO (Either String RDFVarBindingModify)+ssFindVarModify (nam,lbs) = gets $ \st ->+ case findOpenVarModify nam st of+ Just ovbm -> Right (ovbm lbs)+ Nothing -> Left ("Undefined modifier: "++show nam)++ssDefineRuleset ::+ ScopedName+ -> [ScopedName]+ -> [ScopedName]+ -> SwishStateIO ()+ssDefineRuleset sn ans rns =+ let errmsg1 = "Error in ruleset axiom(s): "+ errmsg2 = "Error in ruleset rule(s): "+ in+ do { let agfs = mapM ssGetFormula ans+ :: SwishStateIO [Either String RDFFormula]+ ; aesg <- agfs -- [Either String RDFFormula]+ ; let eags = sequence aesg :: Either String [RDFFormula]+ ; let erlf = mapM ssFindRule rns+ :: SwishStateIO [Either String RDFRule]+ ; rles <- erlf -- [Either String RDFRule]+ ; let erls = sequence rles :: Either String [RDFRule]+ ; let frs = case (eags,erls) of+ (Left er,_) -> setError (errmsg1++er)+ (_,Left er) -> setError (errmsg2++er)+ (Right ags,Right rls) ->+ modRulesets (mapReplaceOrAdd rs)+ where+ rs = makeRuleset (getScopeNamespace sn) ags rls+ ; modify frs+ }++ssFindRule :: ScopedName -> SwishStateIO (Either String RDFRule)+ssFindRule nam = gets find+ where+ find st = case findRule nam st of+ Nothing -> Left ("Rule not found: "++show nam)+ Just rl -> Right rl++ssDefineConstraints ::+ ScopedName+ -> [SwishStateIO (Either String RDFGraph)]+ -> [ScopedName]+ -> SwishStateIO ()+ssDefineConstraints sn cgfs dtns =+ let errmsg1 = "Error in constraint graph(s): "+ errmsg2 = "Error in datatype(s): "+ in+ do { cges <- sequence cgfs -- [Either String RDFGraph]+ ; let ecgs = sequence cges :: Either String [RDFGraph]+ ; let ecgr = case ecgs of+ Left er -> Left er+ Right [] -> Right emptyRDFGraph+ Right grs -> Right $ foldl1 merge grs+ ; edtf <- mapM ssFindDatatype dtns+ -- [Either String RDFDatatype]+ ; let edts = sequence edtf :: Either String [RDFDatatype]+ ; let frs = case (ecgr,edts) of+ (Left er,_) -> setError (errmsg1++er)+ (_,Left er) -> setError (errmsg2++er)+ (Right cgr,Right dts) ->+ modRulesets (mapReplaceOrAdd rs)+ where+ rs = makeRuleset (getScopeNamespace sn) [] rls+ rls = concatMap (`typeMkRules` cgr) dts+ ; modify frs+ }++ssFindDatatype :: ScopedName -> SwishStateIO (Either String RDFDatatype)+ssFindDatatype nam = gets find+ where+ find st = case findDatatype nam st of+ Nothing -> Left ("Datatype not found: "++show nam)+ Just dt -> Right dt+++ssCheckProof ::+ ScopedName -- proof name+ -> [ScopedName] -- ruleset names+ -> SwishStateIO (Either String RDFFormula) -- input formula+ -> [Either String [RDFRuleset] -- proof step from rulesets+ -> SwishStateIO (Either String RDFProofStep)]+ -> SwishStateIO (Either String RDFFormula) -- result formula+ -> SwishStateIO ()+ssCheckProof pn sns igf stfs rgf =+ let+ infmsg1 = "Proof satisfied: "+ errmsg1 = "Error in proof ruleset(s): "+ errmsg2 = "Error in proof input: "+ errmsg3 = "Error in proof step(s): "+ errmsg4 = "Error in proof goal: "+ errmsg5 = "Proof not satisfied: "+ proofname = " (Proof "++show pn++")"+ in+ do { let rs1 = map ssFindRuleset sns :: [SwishStateIO (Either String RDFRuleset)]+ ; rs2 <- sequence rs1 -- [Either String RDFRuleset]+ ; let erss = sequence rs2 :: Either String [RDFRuleset]+ ; eig <- igf -- Either String RDFFormula+ ; let st1 = sequence $ flist stfs erss :: SwishStateIO [Either String RDFProofStep]+ ; st2 <- st1 -- [Either String RDFProofStep]+ ; let ests = sequence st2 :: Either String [RDFProofStep]+ ; erg <- rgf -- Either String RDFFormula+ ; let proof = case (erss,eig,ests,erg) of+ (Left er,_,_,_) -> Left (errmsg1++er++proofname)+ (_,Left er,_,_) -> Left (errmsg2++er++proofname)+ (_,_,Left er,_) -> Left (errmsg3++er++proofname)+ (_,_,_,Left er) -> Left (errmsg4++er++proofname)+ (Right rss, Right ig, Right sts, Right rg) ->+ Right (makeRDFProof rss ig rg sts)+ ; when False $ case proof of+ (Left _) -> return ()+ (Right pr) -> putResourceData Nothing $+ B.fromLazyText (L.concat ["Proof ", L.pack (show pn), "\n"])+ `mappend`+ B.fromString (showsProof "\n" pr "\n")+ -- TODO: clean up+ ; let checkproof = case proof of+ (Left er) -> setError er+ (Right pr) ->+ case explainProof pr of+ Nothing -> setInfo (infmsg1++show pn)+ Just ex -> setError (errmsg5++show pn++", "++ex)+ {-+ if not $ checkProof pr then+ setError (errmsg5++show pn)+ else+ setInfo (infmsg1++show pn)+ -}+ ; modify checkproof+ }++ssCheckStep ::+ ScopedName -- rule name+ -> [SwishStateIO (Either String RDFFormula)] -- antecedent graph formulae+ -> SwishStateIO (Either String RDFFormula) -- consequent graph formula+ -> Either String [RDFRuleset] -- rulesets+ -> SwishStateIO (Either String RDFProofStep) -- resulting proof step+ssCheckStep _ _ _ (Left er) = return $ Left er+ssCheckStep rn eagf ecgf (Right rss) =+ let+ errmsg1 = "Rule not in proof step ruleset(s): "+ errmsg2 = "Error in proof step antecedent graph(s): "+ errmsg3 = "Error in proof step consequent graph: "+ in+ do { let mrul = getMaybeContextRule rn rss :: Maybe RDFRule+ ; esag <- sequence eagf -- [Either String RDFFormula]]+ ; let eags = sequence esag :: Either String [RDFFormula]+ ; ecg <- ecgf -- Either String RDFFormula+ ; let est = case (mrul,eags,ecg) of+ (Nothing,_,_) -> Left (errmsg1++show rn)+ (_,Left er,_) -> Left (errmsg2++er)+ (_,_,Left er) -> Left (errmsg3++er)+ (Just rul,Right ags,Right cg) ->+ Right $ makeRDFProofStep rul ags cg+ ; return est+ }++ssFwdChain ::+ ScopedName -- ruleset name+ -> ScopedName -- rule name+ -> [SwishStateIO (Either String RDFGraph)] -- antecedent graphs+ -> ScopedName -- consequent graph name+ -> NamespaceMap -- prefixes for new graph+ -> SwishStateIO ()+ssFwdChain sn rn agfs cn prefs =+ let+ errmsg1 = "FwdChain rule error: "+ errmsg2 = "FwdChain antecedent error: "+ in+ do { erl <- ssFindRulesetRule sn rn+ ; aesg <- sequence agfs -- [Either String RDFGraph]+ ; let eags = sequence aesg :: Either String [RDFGraph]+ ; let fcr = case (erl,eags) of+ (Left er,_) -> setError (errmsg1++er)+ (_,Left er) -> setError (errmsg2++er)+ (Right rl,Right ags) ->+ modGraphs (mapReplaceOrAdd (NamedGraph cn [cg]))+ where+ cg = case fwdApply rl ags of+ [] -> emptyRDFGraph+ grs -> setNamespaces prefs $ foldl1 add grs+ ; modify fcr+ }++ssFindRulesetRule ::+ ScopedName -> ScopedName -> SwishStateIO (Either String RDFRule)+ssFindRulesetRule sn rn = gets find+ where+ find st = case findRuleset sn st of+ Nothing -> Left ("Ruleset not found: "++show sn)+ Just rs -> find1 rs+ find1 rs = case getRulesetRule rn rs of+ Nothing -> Left ("Rule not in ruleset: "++show sn++": "++show rn)+ Just rl -> Right rl++ssFindRuleset ::+ ScopedName -> SwishStateIO (Either String RDFRuleset)+ssFindRuleset sn = gets find+ where+ find st = case findRuleset sn st of+ Nothing -> Left ("Ruleset not found: "++show sn)+ Just rs -> Right rs++ssBwdChain ::+ ScopedName -- ruleset name+ -> ScopedName -- rule name+ -> SwishStateIO (Either String RDFGraph) -- consequent graphs+ -> ScopedName -- antecedent alts name+ -> NamespaceMap -- prefixes for new graphs+ -> SwishStateIO ()+ssBwdChain sn rn cgf an prefs =+ let+ errmsg1 = "BwdChain rule error: "+ errmsg2 = "BwdChain goal error: "+ in+ do { erl <- ssFindRulesetRule sn rn+ ; ecg <- cgf -- Either String RDFGraph+ ; let fcr = case (erl,ecg) of+ (Left er,_) -> setError (errmsg1++er)+ (_,Left er) -> setError (errmsg2++er)+ (Right rl,Right cg) ->+ modGraphs (mapReplaceOrAdd (NamedGraph an ags))+ where+ ags = map mergegr (bwdApply rl cg)+ mergegr grs = case grs of+ [] -> emptyRDFGraph+ _ -> setNamespaces prefs $ foldl1 add grs+ ; modify fcr+ }++-- Temporary implementation: just read local file WNH +-- (Add logic to separate filenames from URIs, and+-- attempt HTTP GET, or similar.)+getResourceData :: Maybe URI -> SwishStateIO (Either String L.Text)+getResourceData muri =+ case muri of+ Nothing -> fromStdin+ Just uri -> fromUri uri+ where+ fromStdin =+ do { dat <- lift LIO.getContents+ ; return $ Right dat+ }+ fromUri = fromFile+ fromFile uri | uriScheme uri == "file:" = Right `fmap` lift (LIO.readFile $ uriPath uri)+ | otherwise = error $ "Unsupported file name for read: " ++ show uri+ +-- Temporary implementation: just write local file+-- (Need to add logic to separate filenames from URIs, and+-- attempt HTTP PUT, or similar.)+putResourceData :: Maybe URI -> B.Builder -> SwishStateIO ()+putResourceData muri gsh =+ do { ios <- lift $ IO.try $+ case muri of+ Nothing -> toStdout+ Just uri -> toUri uri+ ; case ios of+ Left ioe -> modify $ setError+ ("Error writing graph: "+++ IO.ioeGetErrorString ioe)+ Right a -> return a+ }+ where+ toStdout = LIO.putStrLn gstr+ toUri uri | uriScheme uri == "file:" = LIO.writeFile (uriPath uri) gstr+ | otherwise = error $ "Unsupported file name for write: " ++ show uri+ gstr = B.toLazyText gsh++{- $syntax++The script syntax is based loosely on Notation3, and the script parser is an+extension of the Notation3 parser in the module "Swish.RDF.N3Parser".+The comment character is @#@ and white space is ignored.++> script := command *+> command := prefixLine |+> nameItem |+> readGraph |+> writeGraph |+> mergeGraphs |+> compareGraphs |+> assertEquiv |+> assertMember |+> defineRule |+> defineRuleset |+> defineConstraints |+> checkProofCmd |+> fwdChain |+> bwdChain ++-}++{- $prefixLine++> prefixLine := @prefix [<prefix>]: <uri> .++Define a namespace prefix and URI. ++The prefix thus defined is available for use in any subsequent script+command, and also in any graphs contained within the script file. (So,+prefix declarations do not need to be repeated for each graph+contained within the script.)++Graphs read from external files must contain their own prefix+declarations.++Example:++ > @prefix gex: <http://example1.com/graphs/>.+ > @prefix : <http://example2.com/id/>.++-}++{- $nameItem++> nameItem := name :- graph |+> name :- ( graph* )++Graphs or lists of graphs can be given a name for use in other+statements. A name is a qname (prefix:local) or a URI enclosed in+angle++Example:++> @prefix ex1: <http://example.com/graphs/> .+> @prefix ex2: <http://example.com/statements/> .+>+> ex1:gr1 :- { +> ex2:foo a ex2:Foo .+> ex2:bar a ex2:Bar .+> ex2:Bar rdfs:subClassOf ex2:Foo .+> }++-}++{- $readGraph++> readGraph := @read name [<uri>]++The @\@read@ command reads in the contents of the given URI+- which at present only supports reading local files, so+no HTTP access - and stores it under the given name.++If no URI is given then the file is read from standard input.++Example:++ > @prefix ex: <http://example.com/> .+ > @read ex:foo <foo.n3>++-}++{- $writeGraph++> writeGraph := @write name [<uri>] ; comment++The @\@write@ command writes out the contents of the given graph+- which at present only supports writing local files, so+no HTTP access. The comment text is written as a comment line+preceeding the graph contents.++If no URI is given then the file is written to the standard output.++Example:++ > @prefix ex: <http://example.com/> .+ > @read ex:gr1 <graph1.n3>+ > @read ex:gr2 <graph2.n3>+ > @merge (ex:gr1 ex:gr2) => ex:gr3+ > @write ex:gr3 ; the merged data+ > @write ex:gr3 <merged.n3> ; merge of graph1.n3 and graph2.n3++-}++{- $mergeGraphs++> mergeGraphs := @merge ( name* ) => name++Create a new named graph that is the merge two or more graphs,+renaming bnodes as required to avoid node-merging.++When the merge command is run, the message++ > # Merge: <output graph name>++will be created on the standard output channel.++Example:++ > @prefix gex: <http://example.com/graph/>.+ > @prefix ex: <http://example.com/statements/>.+ > gex:gr1 :- { ex:foo ex:bar _:b1 . }+ > gex:gr2 :- { _:b1 ex:foobar 23. }+ > @merge (gex:gr1 gex:gr2) => gex:gr3+ > @write gex:gr3 ; merged graphs++When run in Swish, this creates the following output (along with+several other namespace declarations):++ > # merged graphs+ > @prefix ex: <http://example.com/statements/> .+ > ex:foo ex:bar [] .+ > [+ > ex:foobar "23"^^xsd:integer+ > ] .++-}++{- $compareGraphs++> compareGraphs := @compare name name++Compare two graphs for isomorphism, setting the Swish exit status to+reflect the result.++When the compare command is run, the message++ > # Compare: <graph1> <graph2>++will be created on the standard output channel.++Example:++ > @prefix gex: <http://example.com/graphs/>.+ > @read gex:gr1 <graph1.n3>+ > @read gex:gr2 <graph2.n3>+ > @compare gex:gr1 gex:gr2++-}++{- $assertEquiv++> assertEquiv := @asserteq name name ; comment++Test two graphs or lists of graphs for isomorphism, reporting if they+differ. The comment text is included with any report generated.++When the command is run, the message++ > # AssertEq: <comment>++will be created on the standard output channel.++Example:++ > @prefix ex: <http://id.ninebynine.org/wip/2003/swishtest/> .+ >+ > # Set up the graphs for the rules+ > ex:Rule01Ant :- { ?p ex:son ?o . }+ > ex:Rule01Con :- { ?o a ex:Male ; ex:parent ?p . }+ >+ > # Create a rule and a ruleset+ > @rule ex:Rule01 :- ( ex:Rule01Ant ) => ex:Rule01Con+ > @ruleset ex:rules :- (ex:TomSonDick ex:TomSonHarry) ; (ex:Rule01)+ >+ > # Apply the rule+ > @fwdchain ex:rules ex:Rule01 { :Tom ex:son :Charles . } => ex:Rule01fwd+ >+ > # Compare the results to the expected value+ > ex:ExpectedRule01fwd :- { :Charles a ex:Male ; ex:parent :Tom . } + > @asserteq ex:Rule01fwd ex:ExpectedRule01fwd+ > ; Infer that Charles is male and has parent Tom++-}++{- $assertMember++> assertMember := @assertin name name ; comment++Test if a graph is isomorphic to a member of a list of graphs,+reporting if no match is found. The comment text is included with any+report generated.++Example:++> @bwdchain pv:rules :PassengerVehicle ex:Test01Inp <= :t1b+> +> @assertin ex:Test01Bwd0 :t1b ; Backward chain component test (0)+> @assertin ex:Test01Bwd1 :t1b ; Backward chain component test (1)++-}++{- $defineRule++> defineRule := @rule name :- ( name* ) => name+> defineRule := @rule name :- ( name* ) => name+> | ( (name var*)* )++Define a named Horn-style rule. ++The list of names preceding and following @=>@ are the antecedent and consequent+graphs, respectivelu. Both sets may contain variable nodes of the form +@?var@.++The optional part, after the @|@ separator, is a list of variable+binding modifiers, each of which consists of a name and a list of+variables (@?var@) to which the modifier is applied. Variable binding+modifiers are built in to Swish, and are used to incorporate datatype+value inferences into a rule. ++-}++{- $defineRuleset++> defineRuleset := @ruleset name :- ( name* ) ; ( name* ) ++Define a named ruleset (a collection of axioms and rules). The first+list of names are the axioms that are part of the ruleset, and the+second list are the rules.++-}++{- $defineConstraints++> defineConstraints := @constraints pref :- ( name* ) | [ name | ( name* ) ]++Define a named ruleset containing class-restriction rules based on a+datatype value constraint. The first list of+names is a list of graphs that together comprise the class-restriction+definitions (rule names are the names of the corresponding restriction+classes). The second list of names is a list of datatypes whose+datatype relations are referenced by the class restriction+definitions.++-}++{- $fwdChain++> fwdChain := @fwdchain pref name ( name* ) => name++Define a new graph obtained by forward-chaining a rule. The first name+is the ruleset to be used. The second name is the rule name. The list+of names are the antecedent graphs to which the rule is applied. The+name following the @=>@ names a new graph that is the result of formward+chaining from the given antecedents using the indicated rule.++-}++{- $bwdChain++> bwdChain := @bwdchain pref name graph <= name++Define a new list of alternative graphs obtained by backward-chaining+a rule. The first name is the ruleset to be used. The second name is+the rule name. The third name (before the @<=@) is the name of a goal graph+from which to backward chain. The final name (after the @<=@) names a new+list of graphs, each of which is an alternative antecedent from which+the given goal can be deduced using the indicated rule.+++-}++{- $proof++> checkProofCmd := proofLine nl+> inputLine nl+> (stepLine nl)*+> resultLine+> proofLine := @proof name ( name* )++Check a proof, reporting the step that fails, if any.++The @\@proof@ line names the proof and specifies a list rulesets+(proof context) used. The remaining lines specify the input+expression (@\@input@), proof steps (@\@step@) and the final result+(@\@result@) that is demonstrated by the proof.++> inputLine := @input name++In a proof, indicates an input expression upon which the proof is+based. Exactly one of these immediately follows the @\@proof@ command.++> stepLine := @step name ( name* ) => name++This defines a step of the proof; any number of these immediately+follow the @\@input@ command.++It indicates the name of the rule applied for this step, a list of+antecedent graphs, and a named graph that is deduced by this step.+For convenience, the deduced graph may introduce a new named graph+using an expression of the form:++ > name :- { statements }++> resultLine := @result name++This defines the goal of the proof, and completes a proof+definition. Exactly one of these immediately follows the @\@step@+commands. For convenience, the result statement may introduce a new+named graph using an expression of the form:++ > name :- { statements }++-}++{- $exampleScript++This is the example script taken from+<http://www.ninebynine.org/Software/swish-0.2.1.html#sec-script-example>+with the proof step adjusted so that it passes.++> # -- Example Swish script --+> #+> # Comment lines start with a '#'+> #+> # The script syntax is loosely based on Notation3, but it is a quite +> # different language, except that embedded graphs (enclosed in {...})+> # are encoded using Notation3 syntax.+> #+> # -- Prefix declarations --+> #+> # As well as being used for all labels defined and used by the script+> # itself, these are applied to all graph expressions within the script +> # file, and to graphs created by scripted inferences, +> # but are not applied to any graphs read in from an external source.+> +> @prefix ex: <http://id.ninebynine.org/wip/2003/swishtest/> .+> @prefix pv: <http://id.ninebynine.org/wip/2003/swishtest/pv/> .+> @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .+> @prefix xsd_integer: <http://id.ninebynine.org/2003/XMLSchema/integer#> .+> @prefix rs_rdf: <http://id.ninebynine.org/2003/Ruleset/rdf#> .+> @prefix rs_rdfs: <http://id.ninebynine.org/2003/Ruleset/rdfs#> .+> @prefix : <http://id.ninebynine.org/default/> .+> +> # Additionally, prefix declarations are provided automatically for:+> # @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .+> # @prefix rdfs: <file://www.w3.org/2000/01/rdf-schema#> .+> # @prefix rdfd: <http://id.ninebynine.org/2003/rdfext/rdfd#> .+> # @prefix rdfo: <http://id.ninebynine.org/2003/rdfext/rdfo#> .+> # @prefix owl: <http://www.w3.org/2002/07/owl#> .+> +> # -- Simple named graph declarations --+> +> ex:Rule01Ant :- { ?p ex:son ?o . }+> +> ex:Rule01Con :- { ?o a ex:Male ; ex:parent ?p . }+> +> ex:TomSonDick :- { :Tom ex:son :Dick . }+> ex:TomSonHarry :- { :Tom ex:son :Harry . }+> +> # -- Named rule definition --+> +> @rule ex:Rule01 :- ( ex:Rule01Ant ) => ex:Rule01Con+> +> # -- Named ruleset definition --+> #+> # A 'ruleset' is a collection of axioms and rules.+> #+> # Currently, the ruleset is identified using the namespace alone;+> # i.e. the 'rules' in 'ex:rules' below is not used. +> # This is under review.+> +> @ruleset ex:rules :- (ex:TomSonDick ex:TomSonHarry) ; (ex:Rule01)+> +> # -- Forward application of rule --+> #+> # The rule is identified here by ruleset and a name within the ruleset.+> +> @fwdchain ex:rules ex:Rule01 { :Tom ex:son :Charles . } => ex:Rule01fwd+> +> # -- Compare graphs --+> #+> # Compare result of inference with expected result.+> # This is a graph isomorphism test rather than strict equality, +> # to allow for bnode renaming.+> # If the graphs are not equal, a message is generated, which+> # includes the comment (';' to end of line)+> +> ex:ExpectedRule01fwd :- { :Charles a ex:Male ; ex:parent :Tom . } +> +> @asserteq ex:Rule01fwd ex:ExpectedRule01fwd+> ; Infer that Charles is male and has parent Tom+> +> # -- Display graph (to screen and a file) --+> #+> # The comment is included in the output.+> +> @write ex:Rule01fwd ; Charles is male and has parent Tom+> @write ex:Rule01fwd <Example1.n3> ; Charles is male and has parent Tom+> +> # -- Read graph from file --+> #+> # Creates a new named graph in the Swish environment.+> +> @read ex:Rule01inp <Example1.n3>+> +> # -- Proof check --+> #+> # This proof uses the built-in RDF and RDFS rulesets, +> # which are the RDF- and RDFS- entailment rules described in the RDF+> # formal semantics document.+> #+> # To prove:+> # ex:foo ex:prop "a" .+> # RDFS-entails+> # ex:foo ex:prop _:x .+> # _:x rdf:type rdfs:Resource .+> #+> # If the proof is not valid according to the axioms and rules of the +> # ruleset(s) used and antecedents given, then an error is reported +> # indicating the failed proof step.+> +> ex:Input :- { ex:foo ex:prop "a" . }+> ex:Result :- { ex:foo ex:prop _:a . _:a rdf:type rdfs:Resource . }+> +> @proof ex:Proof ( rs_rdf:rules rs_rdfs:rules )+> @input ex:Input+> @step rs_rdfs:r3 ( rs_rdfs:a10 rs_rdfs:a39 )+> => ex:Stepa :- { rdfs:Literal rdf:type rdfs:Class . }+> @step rs_rdfs:r8 ( ex:Stepa )+> => ex:Stepb :- { rdfs:Literal rdfs:subClassOf rdfs:Resource . }+> @step rs_rdf:lg ( ex:Input )+> => ex:Stepc :- { ex:foo ex:prop _:a . _:a rdf:_allocatedTo "a" . }+> @step rs_rdfs:r1 ( ex:Stepc )+> => ex:Stepd :- { _:a rdf:type rdfs:Literal . }+> @step rs_rdfs:r9 ( ex:Stepb ex:Stepd )+> => ex:Stepe :- { _:a rdf:type rdfs:Resource . }+> @step rs_rdf:se ( ex:Stepc ex:Stepd ex:Stepe )+> => ex:Result+> @result ex:Result+> +> # -- Restriction based datatype inferencing --+> #+> # Datatype inferencing based on a general class restriction and+> # a predefined relation (per idea noted by Pan and Horrocks).+> +> ex:VehicleRule :-+> { :PassengerVehicle a rdfd:GeneralRestriction ;+> rdfd:onProperties (:totalCapacity :seatedCapacity :standingCapacity) ;+> rdfd:constraint xsd_integer:sum ;+> rdfd:maxCardinality "1"^^xsd:nonNegativeInteger . }+> +> # Define a new ruleset based on a declaration of a constraint class+> # and reference to built-in datatype.+> # The datatype constraint xsd_integer:sum is part of the definition +> # of datatype xsd:integer that is cited in the constraint ruleset+> # declaration. It relates named properties of a class instance.+> +> @constraints pv:rules :- ( ex:VehicleRule ) | xsd:integer+> +> # Input data for test cases:+> +> ex:Test01Inp :-+> { _:a1 a :PassengerVehicle ;+> :seatedCapacity "30"^^xsd:integer ;+> :standingCapacity "20"^^xsd:integer . }+> +> # Forward chaining test case:+> +> ex:Test01Fwd :- { _:a1 :totalCapacity "50"^^xsd:integer . }+> +> @fwdchain pv:rules :PassengerVehicle ex:Test01Inp => :t1f+> @asserteq :t1f ex:Test01Fwd ; Forward chain test+> +> # Backward chaining test case:+> #+> # Note that the result of backward chaining is a list of alternatives,+> # any one of which is sufficient to derive the given conclusion.+> +> ex:Test01Bwd0 :-+> { _:a1 a :PassengerVehicle .+> _:a1 :totalCapacity "50"^^xsd:integer .+> _:a1 :seatedCapacity "30"^^xsd:integer . }+> +> ex:Test01Bwd1 :-+> { _:a1 a :PassengerVehicle .+> _:a1 :totalCapacity "50"^^xsd:integer .+> _:a1 :standingCapacity "20"^^xsd:integer . }+> +> # Declare list of graphs:+> +> ex:Test01Bwd :- ( ex:Test01Bwd0 ex:Test01Bwd1 )+> +> @bwdchain pv:rules :PassengerVehicle ex:Test01Inp <= :t1b+> @asserteq :t1b ex:Test01Bwd ; Backward chain test+> +> # Can test for graph membership in a list+> +> @assertin ex:Test01Bwd0 :t1b ; Backward chain component test (0)+> @assertin ex:Test01Bwd1 :t1b ; Backward chain component test (1)+> +> # -- Merge graphs --+> #+> # Merging renames bnodes to avoid collisions.+> +> @merge ( ex:Test01Bwd0 ex:Test01Bwd1 ) => ex:Merged+> +> # This form of comparison sets the Swish exit status based on the result.+> +> ex:ExpectedMerged :-+> { _:a1 a :PassengerVehicle .+> _:a1 :totalCapacity "50"^^xsd:integer .+> _:a1 :seatedCapacity "30"^^xsd:integer .+> _:a2 a :PassengerVehicle .+> _:a2 :totalCapacity "50"^^xsd:integer .+> _:a2 :standingCapacity "20"^^xsd:integer . }+> +> @compare ex:Merged ex:ExpectedMerged+> +> # End of example script++If saved in the file example.ss, then it can be evaluated by saying+either of:++> % Swish -s=example.ss++or, from @ghci@:++> Prelude> :m + Swish.RDF.SwishMain+> Prelude Swish.RDF.SwishMain> :set prompt "Swish> "+> Swish> runSwish "-s=example.ss"++and the output is++> # AssertEq: Infer that Charles is male and has parent Tom+> # Charles is male and has parent Tom+> @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .+> @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .+> @prefix rdfd: <http://id.ninebynine.org/2003/rdfext/rdfd#> .+> @prefix owl: <http://www.w3.org/2002/07/owl#> .+> @prefix log: <http://www.w3.org/2000/10/swap/log#> .+> @prefix : <http://id.ninebynine.org/default/> .+> @prefix ex: <http://id.ninebynine.org/wip/2003/swishtest/> .+> @prefix pv: <http://id.ninebynine.org/wip/2003/swishtest/pv/> .+> @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .+> @prefix xsd_integer: <http://id.ninebynine.org/2003/XMLSchema/integer#> .+> @prefix rs_rdf: <http://id.ninebynine.org/2003/Ruleset/rdf#> .+> @prefix rs_rdfs: <http://id.ninebynine.org/2003/Ruleset/rdfs#> .+> :Charles ex:parent :Tom ;+> a ex:Male .+> +> Proof satisfied: ex:Proof+> # AssertEq: Forward chain test+> # AssertEq: Backward chain test+> # AssertIn: Backward chain component test (0)+> # AssertIn: Backward chain component test (1)+> # Merge: ex:Merged+> # Compare: ex:Merged ex:ExpectedMerged++-}++--------------------------------------------------------------------------------+--+-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/TurtleFormatter.hs view
@@ -0,0 +1,885 @@+{-# LANGUAGE OverloadedStrings #-}++--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : TurtleFormatter+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : OverloadedStrings+--+-- This Module implements a Turtle formatter (see [1])+-- for an RDFGraph value.+--+-- REFERENCES:+--+-- 1 <http://www.w3.org/TR/turtle/>+-- Turtle, Terse RDF Triple Language+-- W3C Working Draft 09 August 2011 (<http://www.w3.org/TR/2011/WD-turtle-20110809/>)+--+--------------------------------------------------------------------------------++{-+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.TurtleFormatter+ ( NodeGenLookupMap+ , formatGraphAsText+ , formatGraphAsLazyText+ , formatGraphAsBuilder+ , formatGraphIndent + , formatGraphDiag+ )+where++import Swish.RDF.RDFGraph (+ RDFGraph, RDFLabel(..)+ , NamespaceMap, RevNamespaceMap+ -- emptyNamespaceMap,+ -- FormulaMap, emptyFormulaMap,+ , getArcs+ , labels+ -- , setNamespaces+ , getNamespaces,+ -- getFormulae,+ emptyRDFGraph+ , quote+ , quoteT+ , resRdfFirst, resRdfRest, resRdfNil+ )++import Swish.RDF.Vocabulary (+ isLang+ , langTag + , rdfType+ , rdfNil+ , xsdBoolean, xsdDecimal, xsdInteger, xsdDouble + )++import Swish.RDF.GraphClass (Arc(..))++import Swish.Utils.LookupMap+ ( LookupEntryClass(..)+ , LookupMap, emptyLookupMap, reverseLookupMap+ , listLookupMap+ , mapFind, mapFindMaybe, mapAdd+ -- , mapDelete+ , mapMerge+ )++import Swish.Utils.Namespace (ScopedName, getScopeLocal, getScopeURI)++import Data.Char (isDigit)++import Data.List (foldl', delete, groupBy, partition, sort, intersperse)++import Data.Monoid (Monoid(..))+import Control.Monad (liftM, when)+import Control.Monad.State (State, modify, get, put, runState)++-- 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.Text as T+import qualified Data.Text.Lazy as L+import qualified Data.Text.Lazy.Builder as B++-- temporary conversion+quoteB :: Bool -> String -> B.Builder+quoteB f v = B.fromString $ quote f v++----------------------------------------------------------------------+-- 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).++type SubjTree lb = [(lb,PredTree lb)]+type PredTree lb = [(lb,[lb])]++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)]+ , nodeGenSt :: NodeGenState+ , bNodesCheck :: [RDFLabel] -- these bNodes are not to be converted to '[..]' format+ , traceBuf :: [String]+ }+ +type Formatter a = State TurtleFormatterState a++emptyTFS :: NodeGenState -> TurtleFormatterState+emptyTFS ngs = TFS+ { indent = "\n"+ , lineBreak = False+ , graph = emptyRDFGraph+ , subjs = []+ , props = []+ , objs = []+ -- , formAvail = emptyFormulaMap+ -- , formQueue = []+ , nodeGenSt = ngs+ , bNodesCheck = []+ , traceBuf = []+ }++-- | Node name generation state information that carries through+-- and is updated by nested formulae+type NodeGenLookupMap = LookupMap (RDFLabel,Int)++data NodeGenState = Ngs+ { prefixes :: NamespaceMap+ , nodeMap :: NodeGenLookupMap+ , nodeGen :: Int+ }++emptyNgs :: NodeGenState+emptyNgs = Ngs+ { prefixes = emptyLookupMap+ , nodeMap = emptyLookupMap+ , nodeGen = 0+ }++-- simple context for label creation+-- (may be a temporary solution to the problem+-- of label creation)+--+data LabelContext = SubjContext | PredContext | ObjContext+ deriving (Eq, Show)++getIndent :: Formatter B.Builder+getIndent = indent `liftM` get++setIndent :: B.Builder -> Formatter ()+setIndent ind = modify $ \st -> st { indent = ind }++getLineBreak :: Formatter Bool+getLineBreak = lineBreak `liftM` get++setLineBreak :: Bool -> Formatter ()+setLineBreak brk = modify $ \st -> st { lineBreak = brk }++getNgs :: Formatter NodeGenState+getNgs = nodeGenSt `liftM` get++setNgs :: NodeGenState -> Formatter ()+setNgs ngs = modify $ \st -> st { nodeGenSt = ngs }++getPrefixes :: Formatter NamespaceMap+getPrefixes = prefixes `liftM` getNgs++getSubjs :: Formatter (SubjTree RDFLabel)+getSubjs = subjs `liftM` get++setSubjs :: SubjTree RDFLabel -> Formatter ()+setSubjs sl = modify $ \st -> st { subjs = sl }++getProps :: Formatter (PredTree RDFLabel)+getProps = props `liftM` get++setProps :: PredTree RDFLabel -> Formatter ()+setProps ps = modify $ \st -> st { props = ps }++{-+getObjs :: Formatter ([RDFLabel])+getObjs = objs `liftM` get++setObjs :: [RDFLabel] -> Formatter ()+setObjs os = do+ st <- get+ put $ st { objs = os }+-}++getBnodesCheck :: Formatter [RDFLabel]+getBnodesCheck = bNodesCheck `liftM` get++{-+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 = mapDelete fa fn,+ formQueue = (fn,fv) : formQueue st+ }+ case mapFindMaybe fn fa of+ Nothing -> return ()+ Just v -> put (newState v) >> return ()+-}++{-+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 fa = formAvail st+ newState = st { formAvail=mapDelete fa fn }+ case mapFindMaybe fn fa of+ Nothing -> return Nothing+ Just fv -> put newState >> return (Just fv)++-}++{-+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++-}++-- list has a length of 1+len1 :: [a] -> Bool+len1 (_:[]) = True+len1 _ = False++{-|+Given a set of statements and a label, return the details of the+RDF collection referred to by label, or Nothing.++For label to be considered as representing a collection we require the+following conditions to hold (this is only to support the+serialisation using the '(..)' syntax and does not make any statement+about semantics of the statements with regard to RDF Collections):++ - there must be one rdf_first and one rdfRest statement+ - there must be no other predicates for the label++-} +getCollection :: + SubjTree RDFLabel -- ^ statements organized by subject+ -> RDFLabel -- ^ does this label represent a list?+ -> Maybe (SubjTree RDFLabel, [RDFLabel], [RDFLabel])+ -- ^ the statements with the elements removed; the+ -- content elements of the collection (the objects of the rdf:first+ -- predicate) and the nodes that represent the spine of the+ -- collection (in reverse order, unlike the actual contents which are in+ -- order).+getCollection subjList lbl = go subjList lbl ([],[]) + where+ go sl l (cs,ss) | l == resRdfNil = Just (sl, reverse cs, ss)+ | otherwise = do+ (pList1, sl') <- removeItem sl l+ (pFirst, pList2) <- removeItem pList1 resRdfFirst+ (pNext, pList3) <- removeItem pList2 resRdfRest++ -- QUS: could I include these checks implicitly in the pattern matches above?+ -- ie instrad of (pFirst, pos1) <- ..+ -- have ([content], pos1) <- ...+ -- ?+ if and [len1 pFirst, len1 pNext, null pList3]+ then go sl' (head pNext) (head pFirst : cs, l : ss)+ else Nothing++{-+TODO:++Should we change the preds/objs entries as well?++-}+extractList :: LabelContext -> RDFLabel -> Formatter (Maybe [RDFLabel])+extractList lctxt ln = do+ osubjs <- getSubjs+ oprops <- getProps+ let mlst = getCollection osubjs' ln++ -- we only want to send in rdf:first/rdf:rest here+ fprops = filter ((`elem` [resRdfFirst, resRdfRest]) . fst) oprops++ osubjs' =+ case lctxt of+ SubjContext -> (ln, fprops) : osubjs+ _ -> osubjs ++ -- tr = "extractList " ++ show ln ++ " (" ++ show lctxt ++ ")\n -> osubjs= " ++ show osubjs ++ "\n -> opreds= " ++ show oprops ++ "\n -> mlst= " ++ show mlst ++ "\n"+ -- addTrace tr+ case mlst of+ -- sl is guaranteed to be free of (ln,fprops) here if lctxt is SubjContext+ Just (sl,ls,_) -> do+ setSubjs sl+ when (lctxt == SubjContext) $ setProps $ filter ((`notElem` [resRdfFirst, resRdfRest]) . fst) oprops+ return (Just ls)++ Nothing -> return Nothing+ +{-|+Removes the first occurrence of the item from the+association list, returning it's contents and the rest+of the list, if it exists.+-}+removeItem :: (Eq a) => [(a,b)] -> a -> Maybe (b, [(a,b)])+removeItem os x =+ let (as, bs) = break (\a -> fst a == x) os+ in case bs of+ ((_,b):bbs) -> Just (b, as ++ bbs)+ [] -> Nothing++----------------------------------------------------------------------+-- Define a top-level formatter function:+----------------------------------------------------------------------++formatGraphAsText :: RDFGraph -> T.Text+formatGraphAsText = L.toStrict . formatGraphAsLazyText++formatGraphAsLazyText :: RDFGraph -> L.Text+formatGraphAsLazyText = B.toLazyText . formatGraphAsBuilder+ +formatGraphAsBuilder :: RDFGraph -> B.Builder+formatGraphAsBuilder = formatGraphIndent "\n" True+ +formatGraphIndent :: B.Builder -> Bool -> RDFGraph -> 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, Int, [String])+formatGraphDiag indnt flag gr = + let fg = formatGraph indnt " .\n" False flag gr+ ngs = emptyNgs {+ prefixes = emptyLookupMap,+ 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 ind end dobreak dopref gr = do+ setIndent ind+ setLineBreak dobreak+ setGraph gr+ + fp <- if dopref+ then formatPrefixes (getNamespaces gr)+ else return mempty+ more <- moreSubjects+ if more+ then do+ fr <- formatSubjects+ return $ mconcat [fp, fr, end]+ else return fp++formatPrefixes :: NamespaceMap -> Formatter B.Builder+formatPrefixes pmap = do+ let mls = map (pref . keyVal) (listLookupMap pmap)+ ls <- sequence mls+ return $ mconcat ls+ where+ pref (Just p,u) = nextLine $ mconcat ["@prefix ", B.fromText p, ": <", quoteB True (show u), "> ."]+ pref (_,u) = nextLine $ mconcat ["@prefix : <", quoteB True (show u), "> ."]++{-+NOTE:+I expect there to be confusion below where I need to+convert from Text to Builder+-}++formatSubjects :: Formatter B.Builder+formatSubjects = do+ sb <- nextSubject+ sbstr <- formatLabel SubjContext sb+ + flagP <- moreProperties+ if flagP+ then do+ prstr <- formatProperties sb sbstr+ flagS <- moreSubjects+ if flagS+ then do+ fr <- formatSubjects+ return $ mconcat [prstr, " .", fr]+ else return prstr+ + else do+ txt <- nextLine sbstr+ + flagS <- moreSubjects+ if flagS+ then do+ fr <- formatSubjects+ return $ mconcat [txt, " .", fr]+ else return txt++{-+TODO: now we are throwing a Builder around it is awkward to+get the length of the text to calculate the indentation++So++ a) change the indentation scheme+ b) pass around text instead of builder++mkIndent :: L.Text -> L.Text+mkIndent inVal = L.replicate (L.length inVal) " "+-}++hackIndent :: B.Builder+hackIndent = " "++formatProperties :: RDFLabel -> B.Builder -> Formatter B.Builder+formatProperties sb sbstr = do+ pr <- nextProperty sb+ prstr <- formatLabel PredContext pr+ obstr <- formatObjects sb pr $ mconcat [sbstr, " ", prstr]+ more <- moreProperties+ let sbindent = hackIndent -- mkIndent sbstr+ if more+ then do+ fr <- formatProperties sb sbindent+ nl <- nextLine $ obstr `mappend` " ;"+ return $ nl `mappend` fr+ else nextLine obstr++formatObjects :: RDFLabel -> RDFLabel -> B.Builder -> Formatter B.Builder+formatObjects sb pr prstr = do+ ob <- nextObject sb pr+ obstr <- formatLabel ObjContext ob+ more <- moreObjects+ if more+ then do+ let prindent = hackIndent -- mkIndent prstr+ fr <- formatObjects sb pr prindent+ nl <- nextLine $ mconcat [prstr, " ", obstr, ","]+ return $ nl `mappend` fr+ else return $ mconcat [prstr, " ", obstr]++{-+Add a list inline. We are given the labels that constitute+the list, in order, so just need to display them surrounded+by ().+-}+insertList :: [RDFLabel] -> Formatter B.Builder+insertList [] = return "()" -- not convinced this can happen+insertList xs = do+ ls <- mapM (formatLabel ObjContext) xs+ return $ mconcat ("( " : intersperse " " ls) `mappend` " )"+ +{-+Add a blank node inline.+-}++insertBnode :: LabelContext -> RDFLabel -> Formatter B.Builder+insertBnode SubjContext lbl = do+ -- a safety check+ flag <- moreProperties+ if flag+ then do+ txt <- (`mappend` "\n") `liftM` formatProperties lbl ""+ return $ mconcat ["[] ", txt]+ else error $ "Internal error: expected properties with label: " ++ show lbl++insertBnode _ lbl = do+ ost <- get+ let osubjs = subjs ost+ oprops = props ost+ oobjs = objs ost++ (bsubj, rsubjs) = partition ((== lbl) . fst) osubjs++ rprops = case bsubj of+ [(_,rs)] -> rs+ _ -> []++ -- we essentially want to create a new subgraph+ -- for this node but it's not as simple as that since+ -- we could have something like+ -- :a :b [ :foo [ :bar "xx" ] ]+ -- so we still need to carry around the whole graph+ --+ nst = ost { subjs = rsubjs,+ props = rprops,+ objs = []+ }++ put nst+ flag <- moreProperties+ txt <- if flag+ then (`mappend` "\n") `liftM` formatProperties lbl ""+ else return ""++ -- TODO: how do we restore the original set up?+ -- I can't believe the following is sufficient+ --+ nst' <- get+ let slist = map fst $ subjs nst'+ nsubjs = filter (\(l,_) -> l `elem` slist) osubjs++ put $ nst' { subjs = nsubjs,+ props = oprops, + objs = oobjs+ }++ -- TODO: handle indentation?+ return $ mconcat ["[", txt, "]"]+ +----------------------------------------------------------------------+-- Formatting helpers+----------------------------------------------------------------------++setGraph :: RDFGraph -> Formatter ()+setGraph gr = do+ st <- get++ let ngs0 = nodeGenSt st+ pre' = mapMerge (prefixes ngs0) (getNamespaces gr)+ ngs' = ngs0 { prefixes = pre' }+ arcs = sortArcs $ getArcs gr+ nst = st { graph = gr+ , subjs = arcTree arcs+ , props = []+ , objs = []+ -- , formAvail = getFormulae gr+ , nodeGenSt = ngs'+ , bNodesCheck = countBnodes arcs+ }++ put nst++hasMore :: (TurtleFormatterState -> [b]) -> Formatter Bool+hasMore lens = (not . null . lens) `liftM` get++moreSubjects :: Formatter Bool+moreSubjects = hasMore subjs+-- moreSubjects = (not . null . subjs) `liftM` get++moreProperties :: Formatter Bool+moreProperties = hasMore props+-- moreProperties = (not . null . props) `liftM` get++moreObjects :: Formatter Bool+moreObjects = hasMore objs+-- moreObjects = (not . null . objs) `liftM` get++nextSubject :: Formatter RDFLabel+nextSubject = do+ st <- get++ let sb:sbs = subjs st+ nst = st { subjs = sbs+ , props = snd sb+ , objs = []+ }++ put nst+ return $ fst sb++nextProperty :: RDFLabel -> Formatter RDFLabel+nextProperty _ = do+ st <- get++ let pr:prs = props st+ nst = st { props = prs+ , objs = snd pr+ }++ put nst+ return $ fst pr++nextObject :: RDFLabel -> RDFLabel -> Formatter RDFLabel+nextObject _ _ = do+ st <- get++ let ob:obs = objs st+ nst = st { objs = obs }++ put nst+ return ob++nextLine :: B.Builder -> Formatter B.Builder+nextLine str = do+ ind <- getIndent+ brk <- getLineBreak+ if brk+ then return $ ind `mappend` str+ else do+ -- After first line, always insert line break+ setLineBreak True+ return str++-- 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 <uri>+-- (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.+--+--+-- 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.+-}+formatLabel lctxt lab@(Blank (_:_)) = do+ mlst <- extractList lctxt lab+ case mlst of+ Just lst -> insertList 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 <- getBnodesCheck+ 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 = do+ pr <- getPrefixes+ let nsuri = getScopeURI sn+ local = getScopeLocal sn+ premap = reverseLookupMap pr :: RevNamespaceMap+ prefix = mapFindMaybe nsuri premap+ + name = case prefix of+ Just (Just p) -> B.fromText $ quoteT True $ mconcat [p, ":", local] -- TODO: what are quoting rules for QNames+ _ -> mconcat ["<", quoteB True (show nsuri ++ T.unpack local), ">"]+ + return name++-- The canonical notation for xsd:double in XSD, with an upper-case E,+-- does not match the syntax used in N3, so we need to convert here. +-- Rather than converting back to a Double and then displaying that +-- we just convert E to e for now. +-- +formatLabel _ (Lit lit (Just dtype)) + | dtype == xsdDouble = return $ B.fromText $ T.toLower lit+ | dtype `elem` [xsdBoolean, xsdDecimal, xsdInteger] = return $ B.fromText lit+ | otherwise = return $ quoteText lit `mappend` formatAnnotation dtype+formatLabel _ (Lit lit Nothing) = return $ quoteText lit++formatLabel _ lab = return $ B.fromString $ show lab++-- the annotation for a literal (ie type or language)+formatAnnotation :: ScopedName -> B.Builder+formatAnnotation a | isLang a = "@" `mappend` B.fromText (langTag a)+ | otherwise = "^^" `mappend` showScopedName a++{-+We have to decide whether to use " or """ to quote+the string.++There is also no need to restrict the string to the+ASCII character set; this could be an option but we+can also leave Unicode as is (or at least convert to UTF-8).++If we use """ to surround the string then we protect the+last character if it is a " (assuming it isn't protected).+-}++quoteText :: T.Text -> B.Builder+quoteText txt = + let st = T.unpack txt -- TODO: fix+ qst = quoteB (n==1) st+ n = if '\n' `elem` st || '"' `elem` st then 3 else 1+ qch = B.fromString (replicate n '"')+ in mconcat [qch, qst, qch]++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 lab = do+ ngs <- getNgs+ let cmap = nodeMap ngs+ cval = nodeGen ngs+ nv <- case mapFind 0 lab cmap of+ 0 -> do + let nval = succ cval+ nmap = mapAdd cmap (lab, nval)+ setNgs $ ngs { nodeGen = nval, nodeMap = nmap }+ return nval+ + n -> return n+ + -- TODO: is this what we want?+ return $ "_:swish" `mappend` B.fromString (show nv)++-- TODO: need to be a bit more clever with this than we did in NTriples+-- not sure the following counts as clever enough ...+-- +showScopedName :: ScopedName -> B.Builder+{-+showScopedName (ScopedName n l) = + let uri = nsURI n ++ l+ in quote uri+-}+showScopedName = quoteB True . show++----------------------------------------------------------------------+-- Graph-related helper functions+----------------------------------------------------------------------++newtype SortedArcs lb = SA [Arc lb]++sortArcs :: (Ord lb) => [Arc lb] -> SortedArcs lb+sortArcs = SA . sort++-- Rearrange a list of arcs into a tree of pairs which group together+-- all statements for a single subject, and similarly for multiple+-- objects of a common predicate.+--+arcTree :: (Eq lb) => SortedArcs lb -> SubjTree lb+arcTree (SA as) = commonFstEq (commonFstEq id) $ map spopair as+ where+ spopair (Arc s p o) = (s,(p,o))++{-+arcTree as = map spopair $ sort as+ where+ spopair (Arc s p o) = (s,[(p,[o])])+-}++-- Rearrange a list of pairs so that multiple occurrences of the first+-- are commoned up, and the supplied function is applied to each sublist+-- with common first elements to obtain the corresponding second value+commonFstEq :: (Eq a) => ( [b] -> c ) -> [(a,b)] -> [(a,c)]+commonFstEq f ps =+ [ (fst $ head sps,f $ map snd sps) | sps <- groupBy fstEq ps ]+ where+ fstEq (f1,_) (f2,_) = f1 == f2++findMaxBnode :: RDFGraph -> Int+findMaxBnode = maximum . map getAutoBnodeIndex . labels++getAutoBnodeIndex :: RDFLabel -> Int+getAutoBnodeIndex (Blank ('_':lns)) = res where+ -- cf. prelude definition of read s ...+ res = case [x | (x,t) <- reads lns, ("","") <- lex t] of+ [x] -> x+ _ -> 0+getAutoBnodeIndex _ = 0++{-+Find all blank nodes that occur+ - any number of times as a subject+ - 0 or 1 times as an object++Such nodes can be output using the "[..]" syntax. To make it simpler+to check we actually store those nodes that can not be expanded.++Note that we do not try and expand any bNode that is used in+a predicate position.++Should probably be using the SubjTree RDFLabel structure but this+is easier for now.++-}++countBnodes :: SortedArcs RDFLabel -> [RDFLabel]+countBnodes (SA as) = snd (foldl' ctr ([],[]) as)+ where+ -- first element of tuple are those blank nodes only seen once,+ -- second element those blank nodes seen multiple times+ --+ inc b@(b1s,bms) l@(Blank _) | l `elem` bms = b+ | l `elem` b1s = (delete l b1s, l:bms)+ | otherwise = (l:b1s, bms)+ inc b _ = b++ -- if the bNode appears as a predicate we instantly add it to the+ -- list of nodes not to expand, even if only used once+ incP b@(b1s,bms) l@(Blank _) | l `elem` bms = b+ | l `elem` b1s = (delete l b1s, l:bms)+ | otherwise = (b1s, l:bms)+ incP b _ = b++ ctr orig (Arc _ p o) = inc (incP orig p) o++--------------------------------------------------------------------------------+--+-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/TurtleParser.hs view
@@ -0,0 +1,1062 @@+{-# LANGUAGE OverloadedStrings #-} -- only used in 'fromMaybe "" mbase' line of parseN3++--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : TurtleParser+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : OverloadedStrings+--+-- This Module implements a Turtle parser (see [1]), returning a+-- new 'RDFGraph' consisting of triples and namespace information parsed from+-- the supplied input string, or an error indication.+--+-- REFERENCES:+--+-- 1 <http://www.w3.org/TR/turtle/>+-- Turtle, Terse RDF Triple Language+-- W3C Working Draft 09 August 2011 (<http://www.w3.org/TR/2011/WD-turtle-20110809/>)+--+-- Notes:+--+-- At present there is a lot of overlap with the N3 Parser.+--+--------------------------------------------------------------------------------++module Swish.RDF.TurtleParser+ ( ParseResult+ , parseTurtle + , parseTurtlefromText + {-+ , parseAnyfromText+ , parseTextFromText, parseAltFromText+ , parseNameFromText -- , parsePrefixFromText+ , parseAbsURIrefFromText, parseLexURIrefFromText, parseURIref2FromText+ -}+ + {-+ -- * Exports for parsers that embed Turtle in a bigger syntax+ , TurtleParser, TurtleState(..), 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.RDF.RDFGraph+ ( RDFGraph, RDFLabel(..)+ , ToRDFLabel(..)+ , NamespaceMap+ , addArc + , setNamespaces+ , emptyRDFGraph+ )++import Swish.RDF.GraphClass (arc)++import Swish.Utils.LookupMap+ ( LookupMap(..)+ , LookupEntryClass(..)+ , mapFindMaybe, mapReplaceOrAdd, mapAdd, mapReplace )++import Swish.Utils.Namespace+ ( Namespace, makeNamespace+ , ScopedName+ , getScopeNamespace+ , getScopedNameURI+ , getScopeNamespace+ , makeURIScopedName+ , makeNSScopedName+ )++import Swish.RDF.Vocabulary+ ( langName+ , rdfType+ , rdfFirst, rdfRest, rdfNil+ , xsdBoolean, xsdInteger, xsdDecimal, xsdDouble+ , defaultBase+ )++import Swish.RDF.RDFParser+ ( ParseResult+ , runParserWithError+ , ignore+ , noneOf+ , char+ , ichar+ , string+ , stringT+ , symbol+ , isymbol+ , lexeme+ , whiteSpace+ , mkTypedLit+ , hex4 + , hex8 + , appendURIs+ )++import Control.Applicative+import Control.Monad (foldM)++import Network.URI (URI(..), parseURIReference)++import Data.Char (ord) +import Data.Maybe (fromMaybe, fromJust)++import qualified Data.Text as T+import qualified Data.Text.Lazy as L+import Text.ParserCombinators.Poly.StateText++----------------------------------------------------------------------+-- 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 :: Int -- blank node id generator+ } deriving 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' = mapReplaceOrAdd (makeNamespace 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 = mapFindMaybe 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 "#"++emptyState :: + Maybe URI -- ^ starting base for the graph+ -> TurtleState+emptyState mbase = + let pmap = LookupMap [makeNamespace 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+br :: String -> String -> TurtleParser a -> TurtleParser a+br lsym rsym = bracket (symbol lsym) (symbol rsym)++-- this is a lot simpler than N3+atWord :: T.Text -> TurtleParser ()+atWord s = char '@' *> lexeme (stringT s) *> pure ()++{-+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@(Lit _ (Just dtype)) | dtype `elem` [xsdBoolean, xsdInteger, xsdDecimal, xsdDouble] = do + ost <- stGet+ let stmt = arc s p o+ oldp = prefixUris ost+ ogs = graphState ost+ newp = mapReplaceOrAdd (getScopeNamespace dtype) 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 c = c >= 'a' && c <= 'z'+isAZ c = c >= 'A' && c <= 'Z'+isaZ c = isaz c || isAZ c+is09 c = c >= '0' && c <= '9'+isaZ09 c = isaZ c || is09 c++{-+Convert a string representing a double into canonical+XSD form. The input string is assumed to be syntactically+valid so we use read rather than reads. We use the String read+rather than Text one because of issues I have had in some tests+with the accuracy of the Text one.+-}+d2s :: L.Text -> RDFLabel+d2s = + let conv :: String -> Double+ conv = read+ in toRDFLabel . conv . L.unpack++{-+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+ mapReplaceOrAdd for the input namespace.+ +-}+operatorLabel :: ScopedName -> TurtleParser RDFLabel+operatorLabel snam = do+ st <- stGet+ let sns = getScopeNamespace snam+ opmap = prefixUris st+ pkey = entryKey sns+ pval = entryVal sns+ + rval = Res snam+ + -- the lookup and the replacement could be fused+ case mapFindMaybe pkey opmap of+ Just val | val == pval -> return rval+ | otherwise -> do+ stUpdate $ \s -> s { prefixUris = mapReplace opmap sns }+ return rval+ + _ -> do+ stUpdate $ \s -> s { prefixUris = mapAdd opmap sns }+ return rval+ +findPrefix :: T.Text -> TurtleParser Namespace+findPrefix pre = do+ st <- stGet+ case mapFindMaybe (Just pre) (prefixUris st) of+ Just uri -> return $ makeNamespace (Just pre) uri+ Nothing -> failBad $ "Undefined prefix '" ++ T.unpack pre ++ ":'."++{-++Syntax productions; the Turtle NBF grammar elements are from+http://www.w3.org/TR/turtle/turtle.bnf++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) *> fullStop++{-+[3] directive ::= prefixID + | base+-}+directive :: TurtleParser ()+directive = lexeme (prefixID <|> base)++{-+[4] prefixID ::= PREFIX PNAME_NS IRI_REF +-}+prefixID :: TurtleParser ()+prefixID = do+ _prefix + p <- lexeme _pnameNS+ u <- _iriRef+ stUpdate (setPrefix (fmap L.toStrict p) u)++{-+[5] base ::= BASE IRI_REF +-}+base :: TurtleParser ()+base = _base >> _iriRef >>= stUpdate . setBase++{-+[6] triples ::= subject predicateObjectList +-}++triples :: TurtleParser ()+triples = subject >>= predicateObjectList++{-+[7] predicateObjectList ::= verb objectList ( ";" verb objectList )* (";")? +-}++predicateObjectList :: RDFLabel -> TurtleParser ()+predicateObjectList subj = + let term = verb >>= objectList subj+ in sepBy1 term semiColon *> ignore (optional semiColon)+ -- in sepBy1 (lexeme term) semiColon *> ignore (optional 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 ::= IRIref + | blank +-}++subject :: TurtleParser RDFLabel+subject = (Res <$> iriref) <|> blank++{-+[11] predicate ::= IRIref +-}++predicate :: TurtleParser RDFLabel+predicate = Res <$> iriref++{-+[12] object ::= IRIref + | blank + | literal+-}++object :: TurtleParser RDFLabel+object = (Res <$> iriref) <|> blank <|> literal++{-+[13] literal ::= RDFLiteral + | NumericLiteral + | BooleanLiteral +-}++literal :: TurtleParser RDFLabel+literal = lexeme $ rdfLiteral <|> numericLiteral <|> booleanLiteral++{-+[14] blank ::= BlankNode + | blankNodePropertyList + | collection ++Since both BlankNode and blankNodePropertyList can match '[ ... ]' we pull+that out and treat this as++ blank ::= BLANK_NODE_LABEL+ | "[" (predicateObjectList | WS*) "]"+ | collection++blank :: TurtleParser RDFLabel+blank = lexeme (blankNode <|> blankNodePropertyList <|> collection)++-}++blank :: TurtleParser RDFLabel+blank = lexeme (_blankNodeLabel+ <|>+ bracket (char '[') (char ']') handleBlankNode+ <|>+ collection+ )++{-+[15] blankNodePropertyList ::= "[" predicateObjectList "]" ++We now match the brackets in the parent rule.++blankNodePropertyList :: TurtleParser RDFLabel+blankNodePropertyList = do+ bNode <- newBlankNode+ -- br "[" "]" (predicateObjectList bNode)+ bracket (satisfy (=='['))+ (satisfy (==']'))+ (_manyws *> predicateObjectList bNode *> _manyws)+ -- ignore (optional _manyws) -- TODO: this is a hack+ return bNode++-}++handleBlankNode :: TurtleParser RDFLabel+handleBlankNode = do+ bNode <- newBlankNode+ _manyws+ ignore $ optional $ predicateObjectList bNode+ _manyws+ return bNode+++{-+[16] 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++{-+[17] <BASE> ::= "@base" +-}++_base :: TurtleParser ()+_base = atWord "base"++{-+[18] <PREFIX> ::= "@prefix" +-}++_prefix :: TurtleParser ()+_prefix = atWord "prefix"++{-+[19] <UCHAR> ::= ( "\\u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] ) + | ( "\\U" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] + [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] ) ++-}++_uchar :: TurtleParser Char+_uchar = char '\\' *> _uchar'+ +_uchar' :: TurtleParser Char+_uchar' = (char 'u' *> hex4) <|> (char 'U' *> hex8)++{-+[60s] RDFLiteral ::= String ( LANGTAG | ( "^^" IRIref ) )? +-}++rdfLiteral :: TurtleParser RDFLabel+rdfLiteral = do+ lbl <- turtleString+ opt <- optional (_langTag <|> (string "^^" *> iriref))+ return $ Lit (L.toStrict lbl) opt++{-+[61s] NumericLiteral ::= NumericLiteralUnsigned + | NumericLiteralPositive + | NumericLiteralNegative +-}++numericLiteral :: TurtleParser RDFLabel+numericLiteral = numericLiteralNegative <|> numericLiteralPositive <|> numericLiteralUnsigned++{-+[62s] NumericLiteralUnsigned ::= INTEGER + | DECIMAL + | DOUBLE +-}++numericLiteralUnsigned :: TurtleParser RDFLabel+numericLiteralUnsigned = + d2s <$> _double+ <|> + (mkTypedLit xsdDecimal . L.toStrict <$> _decimal)+ <|> + (mkTypedLit xsdInteger . L.toStrict <$> _integer)++{-+[63s] NumericLiteralPositive ::= INTEGER_POSITIVE + | DECIMAL_POSITIVE + | DOUBLE_POSITIVE +-}++numericLiteralPositive :: TurtleParser RDFLabel+numericLiteralPositive =+ d2s <$> _doublePositive+ <|> + (mkTypedLit xsdDecimal . L.toStrict <$> _decimalPositive)+ <|> + (mkTypedLit xsdInteger . L.toStrict <$> _integerPositive)++{-+[64s] NumericLiteralNegative ::= INTEGER_NEGATIVE + | DECIMAL_NEGATIVE + | DOUBLE_NEGATIVE +-}++numericLiteralNegative :: TurtleParser RDFLabel+numericLiteralNegative = + d2s <$> _doubleNegative+ <|> + (mkTypedLit xsdDecimal . L.toStrict <$> _decimalNegative)+ <|> + (mkTypedLit xsdInteger . L.toStrict <$> _integerNegative)+ +{-+[65s] BooleanLiteral ::= "true" + | "false"+-}++booleanLiteral :: TurtleParser RDFLabel+booleanLiteral = mkTypedLit xsdBoolean . T.pack <$> (string "true" <|> string "false")++{-+[66s] String ::= STRING_LITERAL1 + | STRING_LITERAL2 + | STRING_LITERAL_LONG1 + | STRING_LITERAL_LONG2+-}++turtleString :: TurtleParser L.Text+turtleString = + lexeme (+ _stringLiteralLong1 <|> _stringLiteral1 <|>+ _stringLiteralLong2 <|> _stringLiteral2)++{-+[67s] IRIref ::= IRI_REF + | PrefixedName +-}++iriref :: TurtleParser ScopedName+iriref = lexeme ((makeURIScopedName <$> _iriRef) <|> prefixedName)++{-+[68s] PrefixedName ::= PNAME_LN + | PNAME_NS +-}++prefixedName :: TurtleParser ScopedName+prefixedName = + _pnameLN <|> + flip makeNSScopedName T.empty <$> (_pnameNS >>= findPrefixNamespace)++{-+[69s] BlankNode ::= BLANK_NODE_LABEL + | ANON ++blankNode :: TurtleParser RDFLabel+blankNode = lexeme (_blankNodeLabel <|> _anon)++-}++{-+[70s] <IRI_REF> ::= "<" ( [^<>\"{}|^`\\] - [#0000- ] | UCHAR )* ">" ++Read [#0000- ] as [#x00-#x20] from+http://lists.w3.org/Archives/Public/public-rdf-comments/2011Aug/0011.html++Unlike N3, whitespace is significant within the surrounding <>.++At present relying on Network.URI to define what characters are valid+in a URI. This is not necessarily ideal.+-}++_iriRef :: TurtleParser URI+_iriRef = do+ utxt <- bracket (char '<') (char '>') $ manySatisfy (/= '>') -- TODO: fix+ let ustr = L.unpack utxt+ case parseURIReference ustr of+ Nothing -> fail $ "Unable to convert <" ++ ustr ++ "> to a URI"+ Just uref -> do+ s <- stGet+ either fail return $ appendURIs (baseUri s) uref++{-+[71s] <PNAME_NS> ::= (PN_PREFIX)? ":" +-}++_pnameNS :: TurtleParser (Maybe L.Text)+_pnameNS = optional _pnPrefix <* char ':'++{-+[72s] <PNAME_LN> ::= PNAME_NS PN_LOCAL +-}++_pnameLN :: TurtleParser ScopedName+_pnameLN = makeNSScopedName + <$> (_pnameNS >>= findPrefixNamespace) + <*> fmap L.toStrict _pnLocal++{-+[73s] <BLANK_NODE_LABEL> ::= "_:" PN_LOCAL +-}++_blankNodeLabel :: TurtleParser RDFLabel+_blankNodeLabel = (Blank . L.unpack) <$> (string "_:" *> _pnLocal)++{-++These are unused in the grammar.++[74s] <VAR1> ::= "?" VARNAME +[75s] <VAR2> ::= "$" VARNAME +-}++{-+[76s] <LANGTAG> ::= BASE + | PREFIX + | "@" [a-zA-Z]+ ( "-" [a-zA-Z0-9]+ )* ++I am ignoring the BASE and PREFIX lines here as they don't make sense to me.+-}++_langTag :: TurtleParser ScopedName+_langTag = do+ ichar '@'+ h <- many1Satisfy isaZ+ mt <- optional (L.cons <$> char '-' <*> many1Satisfy isaZ09)+ return $ langName $ L.toStrict $ L.append h (fromMaybe L.empty mt)+ +{-+[77s] <INTEGER> ::= [0-9]+ +-}++_integer :: TurtleParser L.Text+_integer = many1Satisfy is09++{-+[78s] <DECIMAL> ::= [0-9]+ "." [0-9]* + | "." [0-9]+ ++We try to produce a canonical form for the+numbers.+-}++_decimal :: TurtleParser L.Text+_decimal = + let dpart = L.cons <$> char '.' <*> (fromMaybe "0" <$> optional _integer)+ in + (L.append <$> _integer <*> dpart)+ <|>+ (L.append "0." <$> (char '.' *> _integer))++{-+[79s] <DOUBLE> ::= [0-9]+ "." [0-9]* EXPONENT + | "." ( [0-9] )+ EXPONENT + | ( [0-9] )+ EXPONENT ++Unlike _decimal, the canonical form is enforced+later on, although it could be done here.+-}++_double :: TurtleParser L.Text+_double = + (L.append <$> _decimal <*> _exponent)+ <|>+ (L.append <$> _integer <*> _exponent)+ +{-+[80s] <INTEGER_POSITIVE> ::= "+" INTEGER +[81s] <DECIMAL_POSITIVE> ::= "+" DECIMAL +[82s] <DOUBLE_POSITIVE> ::= "+" DOUBLE +-}++_integerPositive, _decimalPositive, _doublePositive :: TurtleParser L.Text+_integerPositive = char '+' *> _integer+_decimalPositive = char '+' *> _decimal+_doublePositive = char '+' *> _double++{-+[83s] <INTEGER_NEGATIVE> ::= "-" INTEGER +[84s] <DECIMAL_NEGATIVE> ::= "-" DECIMAL +[85s] <DOUBLE_NEGATIVE> ::= "-" DOUBLE +-}++_integerNegative, _decimalNegative, _doubleNegative :: TurtleParser L.Text+_integerNegative = L.cons <$> char '-' <*> _integer+_decimalNegative = L.cons <$> char '-' <*> _decimal+_doubleNegative = L.cons <$> char '-' <*> _double++{-+[86s] <EXPONENT> ::= [eE] [+-]? [0-9]+ +-}++_exponent :: TurtleParser L.Text+_exponent = do+ ignore $ satisfy (`elem` "eE")+ ms <- optional (satisfy (`elem` "+-"))+ e <- _integer+ case ms of+ Just '-' -> return $ L.append "E-" e+ _ -> return $ L.cons 'E' e+ +{-+[87s] <STRING_LITERAL1> ::= "'" ( ( [^'\\\n\r] ) | ECHAR | UCHAR )* "'" +[88s] <STRING_LITERAL2> ::= '"' ( ( [^\"\\\n\r] ) | ECHAR | UCHAR )* '"' +[89s] <STRING_LITERAL_LONG1> ::= "'''" ( ( "'" | "''" )? ( [^'\\] | ECHAR | UCHAR ) )* "'''" +[90s] <STRING_LITERAL_LONG2> ::= '"""' ( ( '"' | '""' )? ( [^\"\\] | ECHAR | UCHAR ) )* '"""' +-}++_stringLiteral1, _stringLiteral2 :: TurtleParser L.Text+_stringLiteral1 = _stringIt sQuot (_tChars "'\\\n\r")+_stringLiteral2 = _stringIt dQuot (_tChars "\"\\\n\r")++_stringLiteralLong1, _stringLiteralLong2 :: TurtleParser L.Text+_stringLiteralLong1 = _stringItLong sQuot3 (_tCharsLong '\'' "'\\")+_stringLiteralLong2 = _stringItLong dQuot3 (_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 = (char '\\' *> (_echar' <|> _uchar'))+ <|> noneOf excl++_tCharsLong :: Char -> String -> TurtleParser L.Text+_tCharsLong c excl = do+ mq <- optional $ oneOrTwo c+ r <- _tChars excl+ return $ L.append (fromMaybe L.empty mq) (L.singleton r)++oneOrTwo :: Char -> TurtleParser L.Text+oneOrTwo c = do+ a <- char c+ mb <- optional (char c)+ case mb of+ Just b -> return $ L.pack [a,b]+ _ -> return $ L.singleton a++{-+[91s] <ECHAR> ::= "\\" [tbnrf\\\"'] +-}++_echar :: TurtleParser Char+_echar = char '\\' *> _echar'++_echar' :: TurtleParser Char+_echar' = + (char 't' *> pure '\t') <|>+ (char 'b' *> pure '\b') <|>+ (char 'n' *> pure '\n') <|>+ (char 'r' *> pure '\r') <|>+ (char '\\' *> pure '\\') <|>+ (char '"' *> pure '"') <|>+ (char '\'' *> pure '\'')+++{-++Unused.++[92s] <NIL> ::= "(" (WS)* ")" +-}++{-+[93s] <WS> ::= " " + | "\t" + | "\r" + | "\n"++_ws :: TurtleParser ()+_ws = ignore $ satisfy (`elem` " \t\r\n")++-}++_manyws :: TurtleParser ()+_manyws = ignore $ manySatisfy (`elem` " \t\r\n")++{-+[94s] <ANON> ::= "[" (WS)* "]" ++Unused as we do not support the use of ANON in the BlankNode+terminal.++_anon :: TurtleParser RDFLabel+_anon = br "[" "]" _manyws *> newBlankNode++-}++{-+[95s] <PN_CHARS_BASE> ::= [A-Z] + | [a-z] + | [#00C0-#00D6] + | [#00D8-#00F6] + | [#00F8-#02FF] + | [#0370-#037D] + | [#037F-#1FFF] + | [#200C-#200D] + | [#2070-#218F] + | [#2C00-#2FEF] + | [#3001-#D7FF] + | [#F900-#FDCF] + | [#FDF0-#FFFD] + | [#10000-#EFFFF] + | UCHAR ++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 <|> _uchar++{-+[96s] <PN_CHARS_U> ::= PN_CHARS_BASE + | "_"+-}++_pnCharsU :: TurtleParser Char+_pnCharsU = _pnCharsBase <|> char '_'++{-++Only used in VAR1/2 rules which are themselves unused.++Unused in the grammar (other than+[97s] <VARNAME> ::= ( PN_CHARS_U | [0-9] ) ( PN_CHARS_U | [0-9] | #00B7 | [#0300-#036F] | [# + 203F-#2040] )* +-}++{-+[98s] <PN_CHARS> ::= PN_CHARS_U + | "-" + | [0-9] + | #00B7 + | [#0300-#036F] + | [#203F-#2040] +-}++_pnChars :: TurtleParser Char+_pnChars = + _pnCharsU + <|> + satisfy (\c -> let i = ord c + in c == '-' || (c >= '0' && c <= '9') || i == 0xb7 ||+ match i [(0x0300, 0x036f), (0x203f, 0x2040)])++{-+[99s] <PN_PREFIX> ::= PN_CHARS_BASE ( ( PN_CHARS | "." )* PN_CHARS )? +-}++_pnPrefix :: TurtleParser L.Text+_pnPrefix = L.cons <$> _pnCharsBase <*> _pnRest+ +{-+[100s] <PN_LOCAL> ::= ( PN_CHARS_U | [0-9] ) ( ( PN_CHARS | "." )* PN_CHARS )? +-} ++_pnLocal :: TurtleParser L.Text+_pnLocal = L.cons <$> (_pnCharsU <|> satisfy is09) + <*> _pnRest++{-+Extracted from PN_PREFIX and PN_LOCAL is++<PN_REST> :== ( ( PN_CHARS | "." )* PN_CHARS )?++We assume below that the match is only ever done for small strings, so+the cost of the foldr isn't likely to be large. Let's see how well+this assumption holds up.++-}++_pnRest :: TurtleParser L.Text+_pnRest = do+ lbl <- many (_pnChars <|> char '.')+ let (nret, lclean) = clean 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) (L.singleton '.')+ return $ L.pack lclean++{-+Original from ++ chop = go 0 []+ where+ -- go :: State -> Stack -> String -> String+ go 0 _ [] = []+ go 0 _ (x:xs)+ | isSpace x = go 1 [x] xs+ | otherwise = x : go 0 xs++ go 1 ss [] = []+ go 1 ss (x:xs)+ | isSpace c = go 1 (x:ss) xs+ | otherwise = reverse ss ++ x : go 0 xs++-}++--------------------------------------------------------------------------------+--+-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/VarBinding.hs view
@@ -0,0 +1,540 @@+{-# LANGUAGE MultiParamTypeClasses, TypeSynonymInstances, OverloadedStrings #-}+{-# LANGUAGE FlexibleInstances #-} -- needed for ghc 7.12++--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : VarBinding+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : MultiParamTypeClasses, TypeSynonymInstances, OverloadedStrings, FlexibleInstances+--+-- This module defines functions for representing and manipulating query+-- binding variable sets. This is the key data that mediates between+-- query and back substitution when performing inferences. A framework+-- of query variable modifiers is provided that can be used to+-- implement richer inferences, such as filtering of query results,+-- or replacing values based on known relationships.+--+--------------------------------------------------------------------------------++module Swish.RDF.VarBinding+ ( VarBinding(..), nullVarBinding+ , boundVars, subBinding, makeVarBinding+ , applyVarBinding, joinVarBindings, addVarBinding+ , VarBindingModify(..), OpenVarBindingModify+ , vbmCompatibility, vbmCompose+ , composeSequence, findCompositions, findComposition+ , VarBindingFilter(..)+ , makeVarFilterModify+ , makeVarTestFilter, makeVarCompareFilter+ , varBindingId, nullVarBindingModify+ , varFilterDisjunction, varFilterConjunction+ , varFilterEQ, varFilterNE+ )+where++import Swish.Utils.LookupMap+ ( LookupEntryClass(..) + , makeLookupMap, mapFindMaybe+ )++import Swish.Utils.Namespace (ScopedName, getScopeLocal)+import Swish.RDF.Vocabulary (swishName)+import Swish.Utils.ListHelpers (equiv, subset, flist, headOrNothing, permutations)++import Data.Maybe (mapMaybe, fromMaybe, isJust, fromJust, listToMaybe)++import Data.List (find, intersect, union, (\\), foldl')++import Data.Monoid (mconcat)++-- import qualified Data.Text as T++------------------------------------------------------------+-- Query variable bindings+------------------------------------------------------------++-- TODO: is it worth making a Monoid instance of VarBinding?++-- |VarBinding is the type of an arbitrary variable bindings+-- value, where the type of the bound values is not specified.+--+data VarBinding a b = VarBinding+ { vbMap :: a -> Maybe b+ , vbEnum :: [(a,b)]+ , vbNull :: Bool+ }++-- |VarBinding is an instance of class Eq, so that variable+-- bindings can be compared for equivalence+--+instance (Eq a, Eq b) => Eq (VarBinding a b) where+ vb1 == vb2 = vbEnum vb1 `equiv` vbEnum vb2++-- |VarBinding is an instance of class Show, so that variable+-- bindings can be displayed+--+instance (Show a, Show b) => Show (VarBinding a b) where+ show = show . vbEnum++-- | maps no query variables.+--+nullVarBinding :: VarBinding a b+nullVarBinding = VarBinding+ { vbMap = const Nothing+ , vbEnum = []+ , vbNull = True+ }++-- |Return a list of the variables bound by a supplied variable binding+--+boundVars :: VarBinding a b -> [a]+boundVars = map fst . vbEnum++-- |VarBinding subset function, tests to see if one query binding+-- is a subset of another; i.e. every query variable mapping defined+-- by one is also defined by the other.+--+subBinding :: (Eq a, Eq b) => VarBinding a b -> VarBinding a b -> Bool+subBinding vb1 vb2 = vbEnum vb1 `subset` vbEnum vb2++-- |Function to make a variable binding from a list of+-- pairs of variable and corresponding assigned value.+--+makeVarBinding :: (Eq a, Show a, Eq b, Show b) => [(a,b)] -> VarBinding a b+makeVarBinding vrbs =+ if null vrbs then nullVarBinding -- (nullVarBinding :: VarBinding a b)+ else VarBinding+ { vbMap = selectFrom vrbs+ , vbEnum = vrbs+ , vbNull = null vrbs+ }+ where+ selectFrom = flip mapFindMaybe . makeLookupMap+ -- selectFrom bs is the VarBinding lookup function+ {-+ selectFrom :: (Eq a) => [(a,b)] -> a -> Maybe b+ selectFrom [] _ = Nothing+ selectFrom ((v,r):bs) l = if l == v then Just r+ else selectFrom bs l+ -}++-- |Apply query binding to a supplied value, returning the value+-- unchanged if no binding is defined+--+applyVarBinding :: VarBinding a a -> a -> a+applyVarBinding vbind v = fromMaybe v (vbMap vbind v)++-- |Join a pair of query bindings, returning a new binding that+-- maps all variables recognized by either of the input bindings.+-- If the bindings should overlap, such overlap is not detected and+-- the value from the first binding provided is used arbitrarily.+--+joinVarBindings :: (Eq a) => VarBinding a b -> VarBinding a b -> VarBinding a b+joinVarBindings vb1 vb2+ | vbNull vb1 = vb2+ | vbNull vb2 = vb1+ | otherwise = VarBinding+ { vbMap = mv12+ , vbEnum = map (\v -> (v,fromJust (mv12 v))) bv12+ , vbNull = False+ }+ where+ -- flist fs a = map ($ a) fs; see also monad function 'ap'+ mv12 = headOrNothing . filter isJust . flist [ vbMap vb1, vbMap vb2 ]+ bv12 = boundVars vb1 `union` boundVars vb2++-- |Add a single new value to a variable binding and return the resulting+-- new variable binding.+--+addVarBinding :: (Eq a, Show a, Eq b, Show b) => a -> b -> VarBinding a b+ -> VarBinding a b+addVarBinding lb val vbind = joinVarBindings vbind $ makeVarBinding [(lb,val)]++------------------------------------------------------------+-- Datatypes for variable binding modifiers+------------------------------------------------------------++-- |Define the type of a function to modify variable bindings in+-- forward chaining based on rule antecedent matches. This+-- function is used to implement the \"allocated to\" logic described+-- in Appendix B of the RDF semantics document, in which a specific+-- blank node is associated with all matches of some specific value+-- by applications of the rule on a given graph.+-- Use 'id' if no modification of the variable bindings is required.+--+-- This datatype consists of the modifier function itself, which+-- operates on a list of variable bindings rather than a single+-- variable binding (because some modifications share context across+-- a set of bindings), and some additional descriptive information+-- that allows possible usage patterns to be analyzed.+--+-- Some usage patterns (see 'vbmUsage' for more details):+--+-- [filter] all variables are input variables, and the effect+-- of the modifier function is to drop variable bindings that+-- don't satisfy some criterion.+-- Identifiable by an empty element in @vbmUsage@.+--+-- [source] all variables are output variables: a raw query+-- could be viewed as a source of variable bindings.+-- Identifiable by an element of @vbmUsage@ equal to @vbmVocab@.+--+-- [modifier] for each supplied variable binding, one or more+-- new variable bindings may be created that contain the+-- input variables bound as supplied plus some additional variables.+-- Identifiable by an element of @vbmUsage@ some subset of @vbmVocab@.+--+-- A variety of variable usage patterns may be supported by a given+-- modifier: a modifier may be used to define new variable bindings+-- from existing bindings in a number of ways, or simply to check that+-- some required relationship between bindings is satisfied.+-- (Example, for @a + b = c@, any one variable can be deduced from the+-- other two, or all three may be supplied to check that the relationship+-- does indeed hold.)+--+data VarBindingModify a b = VarBindingModify+ { vbmName :: ScopedName+ -- ^Name used to identify this variable binding+ -- modifier when building inference rules.+ , vbmApply :: [VarBinding a b] -> [VarBinding a b]+ -- ^Apply variable binding modifier to a+ -- list of variable bindings, returning a+ -- new list. The result list is not+ -- necessarily the same length as the+ -- supplied list.+ , vbmVocab :: [a] -- ^List of variables used by this modifier.+ -- All results of applying this modifier contain+ -- bindings for these variables.+ , vbmUsage :: [[a]] -- ^List of binding modifier usage patterns+ -- supported. Each pattern is characterized as+ -- a list of variables for which new bindings+ -- may be created by some application of this+ -- modifier, assuming that bindings for all other+ -- variables in @vbmVocab@ are supplied.+ }++-- |Allow a VarBindingModify value to be accessed using a 'LookupMap'.+--+instance LookupEntryClass+ (VarBindingModify a b) ScopedName (VarBindingModify a b)+ where+ keyVal vbm = (vbmName vbm,vbm)+ newEntry (_,vbm) = vbm++-- |Type for variable binding modifier that has yet to be instantiated+-- with respect to the variables that it operates upon.+--+type OpenVarBindingModify lb vn = [lb] -> VarBindingModify lb vn++-- |Extract variable binding name from @OpenVarBindingModify@ value+--+-- (Because only the name is required, the application to an undefined+-- list of variable labels should never be evaluated, as long as the+-- name is not dependent on the variable names in any way.)+--+-- NOT QUITE... some of the functions that create @OpenVarBindingModify@+-- instances also pattern-match the number of labels provided, forcing+-- evaluation of the labels parameter, even though it's not used.+--+openVbmName :: OpenVarBindingModify lb vn -> ScopedName+openVbmName ovbm = vbmName (ovbm (error "Undefined labels in variable binding"))++-- |Allow an @OpenVarBindingModify@ value to be accessed using a @LookupMap@.+--+instance LookupEntryClass+ (OpenVarBindingModify a b) ScopedName (OpenVarBindingModify a b)+ where+ keyVal ovbm = (openVbmName ovbm,ovbm)+ newEntry (_,ovbm) = ovbm++-- |Allow an OpenVarBindingModify value to be accessed using a LookupMap.+--+instance Show (OpenVarBindingModify a b)+ where+ show = show . openVbmName++-- |Variable binding modifier compatibility test.+--+-- Given a list of bound variables and a variable binding modifier, return+-- a list of new variables that may be bound, or @Nothing@.+--+-- Note: if the usage pattern component is well-formed (i.e. all+-- elements different) then at most one element can be compatible with+-- a given input variable set.+--+vbmCompatibility :: (Eq a) => VarBindingModify a b -> [a] -> Maybe [a]+vbmCompatibility vbm vars = find compat (vbmUsage vbm)+ where+ compat = vbmCompatibleVars vars (vbmVocab vbm)++-- |Variable binding usage compatibility test.+--+-- Returns @True@ if the supplied variable bindings can be compatibly+-- processed by a variable binding usage with supplied vocabulary and+-- usage pattern.+--+vbmCompatibleVars ::+ (Eq a) + => [a] -- ^ variables supplied with bindings+ -> [a] -- ^ variables returned with bindings by a modifier+ -> [a] -- ^ variables assigned new bindings by a modifier+ -> Bool+vbmCompatibleVars bvars vocab ovars =+ null (ivars `intersect` ovars) && -- ivars and ovars don't overlap+ null ((vocab \\ ovars) \\ ivars) -- ovars and ivars cover vocab+ where+ ivars = bvars `intersect` vocab++-- |Compose variable binding modifiers.+--+-- Returns @Just a@ new variable binding modifier that corresponds to+-- applying the first supplied modifier and then applying the second+-- one, or @Nothing@ if the two modifiers cannot be compatibly composed.+--+-- NOTE: this function does not, in general, commute.+--+-- NOTE: if there are different ways to achieve the same usage, that+-- usage is currently repeated in the result returned.+--+vbmCompose :: (Eq a) => VarBindingModify a b -> VarBindingModify a b+ -> Maybe (VarBindingModify a b)+vbmCompose+ (VarBindingModify nam1 app1 voc1 use1)+ (VarBindingModify nam2 app2 voc2 use2)+ | not (null use12) = Just VarBindingModify+ { vbmName = swishName $ mconcat ["_", getScopeLocal nam1, "_", getScopeLocal nam2, "_"]+ , vbmApply = app2 . app1+ , vbmVocab = voc1 `union` voc2+ , vbmUsage = use12+ }+ | otherwise = Nothing+ where+ use12 = compatibleUsage voc1 use1 use2++-- |Determine compatible ways in which variable binding modifiers may+-- be combined.+--+-- The total vocabulary of a modifier is the complete set of variables+-- that are used or bound by the modifier. After the modifier has been+-- applied, bindings must exist for all of these variables.+--+-- A usage pattern of a modifier is a set of variables for which new+-- bindings may be generated by the modifier.+--+-- The only way in which two variable binding modifiers can be incompatible+-- with each other is when they both attempt to create a new binding for+-- the same variable. (Note that this does not mean the composition will+-- be compatible with all inputs: see 'vbmCompatibleVars'.)+--+-- NOTE: if there are different ways to achieve the same usage, that+-- usage is currently repeated in the result returned.+--+compatibleUsage ::+ (Eq a)+ => [a] -- ^ the total vocabulary of the first modifier to be applied+ -> [[a]] -- ^ usage patterns for the first modifier+ -> [[a]] -- ^ usage patterns for the second modifier+ -> [[a]] -- ^ a list of possible usage patterns for the composition of+ -- the first modifier with the second modifier, or an empty list if+ -- the modifiers are incompatible.+compatibleUsage voc1 use1 use2 =+ [ u1++u2 | u2 <- use2, null (voc1 `intersect` u2), u1 <- use1 ]++-- |Find all compatible compositions of a list of variable binding+-- modifiers for a given set of supplied bound variables.+findCompositions :: (Eq a) => [VarBindingModify a b] -> [a]+ -> [VarBindingModify a b]+findCompositions vbms vars =+ mapMaybe (composeCheckSequence vars) (permutations vbms)++-- |Compose sequence of variable binding modifiers, and check+-- that the result can be used compatibly with a supplied list+-- of bound variables, returning @Just (composed modifier)@,+-- or @Nothing@.+--+composeCheckSequence :: (Eq a) => [a] -> [VarBindingModify a b]+ -> Maybe (VarBindingModify a b)+composeCheckSequence vars vbms = useWith vars $ composeSequence vbms+ where+ -- Check that a Maybe modifier is compatible for use with an+ -- indicated set of bound variables, and return (Just modifier)+ -- or Nothing.+ useWith _ Nothing = Nothing+ useWith vs v@(Just vbm)+ | isJust $ vbmCompatibility vbm vs = v+ | otherwise = Nothing++-- |Compose sequence of variable binding modifiers.+--+composeSequence :: (Eq a) => [VarBindingModify a b]+ -> Maybe (VarBindingModify a b)+composeSequence [] = Just varBindingId+composeSequence (vbm:vbms) = foldl' composePair (Just vbm) vbms++-- |Compose a pair of variable binding modifiers, returning+-- @Just (composed modifier)@, or @Nothing@.+--+composePair :: (Eq a) => Maybe (VarBindingModify a b) -> VarBindingModify a b+ -> Maybe (VarBindingModify a b)+composePair Nothing _ = Nothing+composePair (Just vbm1) vbm2 = vbmCompose vbm1 vbm2++-- |Return @Just a@ compatible composition of variable binding modifiers+-- for a given set of supplied bound variables, or @Nothing@ if there+-- is no compatible composition+--+findComposition :: (Eq a) => [VarBindingModify a b] -> [a]+ -> Maybe (VarBindingModify a b)+findComposition = listToMaybe `c2` findCompositions+ where+ c2 = (.) . (.) -- compose with function of two arguments++-- |Variable binding modifier that returns exactly those+-- variable bindings presented.+--+varBindingId :: VarBindingModify a b+varBindingId = VarBindingModify+ { vbmName = swishName "varBindingId"+ , vbmApply = id+ , vbmVocab = []+ , vbmUsage = [[]]+ }++-- |Null variable binding modifier+--+-- This is like 'varBindingId' except parameterized by some labels.+-- I think this is redundant, and should be eliminated.+--+nullVarBindingModify :: OpenVarBindingModify a b+nullVarBindingModify lbs = VarBindingModify+ { vbmName = swishName "nullVarBindingModify"+ , vbmApply = id+ , vbmVocab = lbs+ , vbmUsage = [[]]+ }++------------------------------------------------------------+-- Query binding filters+------------------------------------------------------------++-- |VarBindingFilter is a function type that tests to see if+-- a query binding satisfies some criterion.+--+-- 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 below.+data VarBindingFilter a b = VarBindingFilter+ { vbfName :: ScopedName+ , vbfVocab :: [a]+ , vbfTest :: VarBinding a b -> Bool+ }++-- |Make a variable binding modifier from a variable binding filter value.+makeVarFilterModify :: VarBindingFilter a b -> VarBindingModify a b+makeVarFilterModify vbf = VarBindingModify+ { vbmName = vbfName vbf+ , vbmApply = filter (vbfTest vbf)+ , vbmVocab = vbfVocab vbf+ , vbmUsage = [[]]+ }++-- |Make a variable test filter for a named variable using a+-- supplied value testing function.+makeVarTestFilter ::+ ScopedName -> (b -> Bool) -> a -> VarBindingFilter a b+makeVarTestFilter nam vtest var = VarBindingFilter+ { vbfName = nam+ , vbfVocab = [var]+ , vbfTest = \vb -> case vbMap vb var of+ Just val -> vtest val+ _ -> False+ }++-- |Make a variable comparison filter for named variables using+-- a supplied value comparison function.+makeVarCompareFilter ::+ ScopedName -> (b -> b -> Bool) -> a -> a -> VarBindingFilter a b+makeVarCompareFilter nam vcomp v1 v2 = VarBindingFilter+ { vbfName = nam+ , vbfVocab = [v1,v2]+ , vbfTest = \vb -> case (vbMap vb v1,vbMap vb v2) of+ (Just val1, Just val2) -> vcomp val1 val2+ _ -> False+ }++------------------------------------------------------------+-- Declare some generally useful query binding filters+------------------------------------------------------------++-- |This function generates a query binding filter that ensures that+-- two indicated query variables are mapped to the same value.+varFilterEQ :: (Eq b) => a -> a -> VarBindingFilter a b+varFilterEQ =+ makeVarCompareFilter (swishName "varFilterEQ") (==) ++-- |This function generates a query binding filter that ensures that+-- two indicated query variables are mapped to different values.+varFilterNE :: (Eq b) => a -> a -> VarBindingFilter a b+varFilterNE =+ makeVarCompareFilter (swishName "varFilterNE") (/=) ++-- |This function composes a number of query binding filters+-- into a composite filter that accepts any query binding that+-- satisfies at least one of the component values.+varFilterDisjunction :: (Eq a) => [VarBindingFilter a b]+ -> VarBindingFilter a b+varFilterDisjunction vbfs = VarBindingFilter+ { vbfName = swishName "varFilterDisjunction"+ , vbfVocab = foldl1 union (map vbfVocab vbfs)+ , vbfTest = or . flist (map vbfTest vbfs)+ }++-- |This function composes a number of query binding filters+-- into a composite filter that accepts any query binding that+-- satisfies all of the component values.+--+-- The same function could be achieved by composing the component+-- filter-based modifiers, but this function is more convenient+-- as it avoids the need to check for modifier compatibility.+--+varFilterConjunction :: (Eq a) => [VarBindingFilter a b]+ -> VarBindingFilter a b+varFilterConjunction vbfs = VarBindingFilter+ { vbfName = swishName "varFilterConjunction"+ , vbfVocab = foldl1 union (map vbfVocab vbfs)+ , vbfTest = and . flist (map vbfTest vbfs)+ }++--------------------------------------------------------------------------------+--+-- (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/Vocabulary.hs view
@@ -0,0 +1,233 @@+{-# LANGUAGE OverloadedStrings #-}++--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : Vocabulary+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : 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 + , namespaceLang+ -- ** RDF rules + -- | The namespaces refer to RDF rules and axioms. + , scopeRDF+ , scopeRDFS+ , scopeRDFD+ + -- * Miscellaneous routines+ , langName, langTag, isLang+ , swishName+ + -- * Miscellaneous + , 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.RDF.Vocabulary.RDF+import Swish.RDF.Vocabulary.OWL+import Swish.RDF.Vocabulary.XSD++import Swish.Utils.Namespace (Namespace, makeNamespace, ScopedName, getScopeLocal, getScopeNamespace, makeNSScopedName)++import Data.Monoid (mappend, mconcat)+import Data.Maybe (fromMaybe)+import Network.URI (URI, parseURI)++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 ::+ T.Text -- ^ lbl+ -> Namespace + -- ^ Namespace has prefix @xsd_lbl@ and+ -- URI of @http:\/\/id.ninebynine.org\/2003\/XMLSchema\/lbl#@.+namespaceXsdType dtn = 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 <http://www.w3.org/2000/10/swap/math#>.+namespaceMATH :: Namespace+namespaceMATH = toNS "math" "http://www.w3.org/2000/10/swap/math#"++-- | Maps @log@ to <http://www.w3.org/2000/10/swap/log#>.+namespaceLOG :: Namespace+namespaceLOG = toNSU "log" namespaceLOGURI++-- | Maps @daml@ to <http://www.daml.org/2000/10/daml-ont#>.+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++-- | Maps @lang@ to @http:\/\/id.ninebynine.org\/2003\/Swish\/Lang\/@.+namespaceLang :: Namespace+namespaceLang = toNSU "lang" namespaceLangURI+++tU :: String -> URI+tU = fromMaybe (error "Internal error processing namespace URI") . parseURI++namespaceRDFDURI, + namespaceLOGURI,+ namespaceSwishURI, + namespaceLangURI, 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/"+namespaceLangURI = tU "http://id.ninebynine.org/2003/Swish/Lang/" -- To be replaced by urn:ietf:params:lang? +namespaceDefaultURI = tU "http://id.ninebynine.org/default/"++-- | Convert a local name to a scoped name in the @swish@ namespace (`namespaceSwish`).+swishName :: T.Text -> 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).++-- | Convert the label to a scoped name in the @lang@ namespace (`namespaceLang`).+langName :: + T.Text -- ^ The lower-case version of this label is used.+ -> ScopedName+langName = makeNSScopedName namespaceLang . T.toLower++-- | Get the name of the language tag (note that the result is+-- only guaranteed to be semantically valid if 'isLang' returns @True@+-- but that there is no enforcement of this requirement).+langTag :: ScopedName -> T.Text+langTag = getScopeLocal++-- | Is the scoped name in the `namespaceLang` namespace?+isLang :: ScopedName -> Bool+isLang sname = getScopeNamespace sname == namespaceLang++------------------------------------------------------------+-- 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 :: T.Text -> 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 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/Vocabulary/DublinCore.hs view
@@ -0,0 +1,770 @@+{-# 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 (<http://dublincore.org/documents/dcmi-terms/>).+--+--------------------------------------------------------------------------------++module Swish.RDF.Vocabulary.DublinCore+ ( + namespaceDCTERMS+ , namespaceDCELEM+ , namespaceDCAM+ , namespaceDCTYPE+ + -- * Classes+ -- | See the \"Classes\" section at <http://dublincore.org/documents/dcmi-terms/#H6>.+ , 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 <http://dublincore.org/documents/dcmi-terms/#H2>.+ , 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+ -- <http://dublincore.org/documents/dcmi-terms/#H3>.+ + , dcelemcontributor+ , dcelemcoverage+ , dcelemcreator+ , dcelemdate+ , dcelemdescription+ , dcelemformat+ , dcelemidentifier+ , dcelemlanguage+ , dcelempublisher+ , dcelemrelation+ , dcelemrights+ , dcelemsource+ , dcelemsubject+ , dcelemtitle+ , dcelemtype+ + -- * Encoding+ -- | See the \"Vocabulary Encoding Schemes\" section at <http://dublincore.org/documents/dcmi-terms/#H4>.+ + , dctLCSH+ , dctMESH+ , dctDDC+ , dctLCC+ , dctUDC+ , dctDCMIType+ , dctIMT+ , dctTGN+ , dctNLM++ -- * Datatypes+ -- | See the \"Syntax Encoding Schemes\" section at <http://dublincore.org/documents/dcmi-terms/#H5>.++ , dctBox+ , dctISO3166+ , dctISO639_2+ , dctISO639_3+ , dctPeriod+ , dctPoint+ , dctRFC1766+ , dctRFC3066+ , dctRFC4646+ , dctRFC5646+ , dctURI+ , dctW3CDTF+ + -- * Types+ -- | See the \"DCMI Type Vocabulary\" section at <http://dublincore.org/documents/dcmi-terms/#H7>.+ + , dctypeCollection+ , dctypeDataset+ , dctypeEvent+ , dctypeImage+ , dctypeInteractiveResource+ , dctypeService+ , dctypeSoftware+ , dctypeSound+ , dctypeText+ , dctypePhysicalObject+ , dctypeStillImage+ , dctypeMovingImage+ + -- * DCMI Abstract Model + --+ -- | Terms from the DCMI Abstract Model (<http://dublincore.org/documents/dcmi-terms/#H8>). ++ , dcammemberOf+ , dcamVocabularyEncodingScheme+ + ) where++import Swish.Utils.Namespace (Namespace, makeNamespace, ScopedName, makeNSScopedName)++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 <http://purl.org/dc/terms/>.+namespaceDCTERMS :: Namespace+namespaceDCTERMS = toNS "dcterms" "http://purl.org/dc/terms/"++-- | Maps @dcelem@ to the legacy namespace <http://purl.org/dc/elements/1.1/>.+namespaceDCELEM :: Namespace+namespaceDCELEM = toNS "dcelem" "http://purl.org/dc/elements/1.1/"++-- | Maps @dcam@ to <http://purl.org/dc/dcam/>.+namespaceDCAM :: Namespace+namespaceDCAM = toNS "dcam" "http://purl.org/dc/dcam/"++-- | Maps @dctype@ to <http://purl.org/dc/dcmitype/>.+namespaceDCTYPE :: Namespace+namespaceDCTYPE = toNS "dctype" "http://purl.org/dc/dcmitype/"++------------------------------------------------------------+-- Terms+------------------------------------------------------------++toDCT :: T.Text -> ScopedName+toDCT = makeNSScopedName namespaceDCTERMS++toDCE :: T.Text -> ScopedName+toDCE = makeNSScopedName namespaceDCELEM++toDCAM :: T.Text -> ScopedName+toDCAM = makeNSScopedName namespaceDCAM++toDCTYPE :: T.Text -> ScopedName+toDCTYPE = makeNSScopedName namespaceDCTYPE++-- Classes++-- | @dcterms:Agent@ from <http://dublincore.org/documents/dcmi-terms/#classes-Agent>.+dctAgent :: ScopedName+dctAgent = toDCT "Agent"++-- | @dcterms:AgentClass@ from <http://dublincore.org/documents/dcmi-terms/#classes-AgentClass>.+dctAgentClass :: ScopedName+dctAgentClass = toDCT "AgentClass"++-- | @dcterms:BibliographicResource@ from <http://dublincore.org/documents/dcmi-terms/#classes-BibliographicResource>.+dctBibliographicResource :: ScopedName+dctBibliographicResource = toDCT "BibliographicResource"++-- | @dcterms:FileFormat@ from <http://dublincore.org/documents/dcmi-terms/#classes-FileFormat>.+dctFileFormat :: ScopedName+dctFileFormat = toDCT "FileFormat"++-- | @dcterms:Frequency@ from <http://dublincore.org/documents/dcmi-terms/#classes-Frequency>.+dctFrequency :: ScopedName+dctFrequency = toDCT "Frequency"++-- | @dcterms:Jurisdiction@ from <http://dublincore.org/documents/dcmi-terms/#classes-Jurisdiction>.+dctJurisdiction :: ScopedName+dctJurisdiction = toDCT "Jurisdiction"++-- | @dcterms:LicenseDocument@ from <http://dublincore.org/documents/dcmi-terms/#classes-LicenseDocument>.+dctLicenseDocument :: ScopedName+dctLicenseDocument = toDCT "LicenseDocument"++-- | @dcterms:LinguisticSystem@ from <http://dublincore.org/documents/dcmi-terms/#classes-LinguisticSystem>.+dctLinguisticSystem :: ScopedName+dctLinguisticSystem = toDCT "LinguisticSystem"++-- | @dcterms:Location@ from <http://dublincore.org/documents/dcmi-terms/#classes-Location>.+dctLocation :: ScopedName+dctLocation = toDCT "Location"++-- | @dcterms:LocationPeriodOrJurisdiction@ from <http://dublincore.org/documents/dcmi-terms/#classes-LocationPeriodOrJurisdiction>.+dctLocationPeriodOrJurisdiction :: ScopedName+dctLocationPeriodOrJurisdiction = toDCT "LocationPeriodOrJurisdiction"++-- | @dcterms:MediaType@ from <http://dublincore.org/documents/dcmi-terms/#classes-MediaType>.+dctMediaType :: ScopedName+dctMediaType = toDCT "MediaType"++-- | @dcterms:MediaTypeOrExtent@ from <http://dublincore.org/documents/dcmi-terms/#classes-MediaTypeOrExtent>.+dctMediaTypeOrExtent :: ScopedName+dctMediaTypeOrExtent = toDCT "MediaTypeOrExtent"++-- | @dcterms:MethodOfAccrual@ from <http://dublincore.org/documents/dcmi-terms/#classes-MethodOfAccrual>.+dctMethodOfAccrual :: ScopedName+dctMethodOfAccrual = toDCT "MethodOfAccrual"++-- | @dcterms:MethodOfInstruction@ from <http://dublincore.org/documents/dcmi-terms/#classes-MethodOfInstruction>.+dctMethodOfInstruction :: ScopedName+dctMethodOfInstruction = toDCT "MethodOfInstruction"++-- | @dcterms:PeriodOfTime@ from <http://dublincore.org/documents/dcmi-terms/#classes-PeriodOfTime>.+dctPeriodOfTime :: ScopedName+dctPeriodOfTime = toDCT "PeriodOfTime"++-- | @dcterms:PhysicalMedium@ from <http://dublincore.org/documents/dcmi-terms/#classes-PhysicalMedium>.+dctPhysicalMedium :: ScopedName+dctPhysicalMedium = toDCT "PhysicalMedium"++-- | @dcterms:PhysicalResource@ from <http://dublincore.org/documents/dcmi-terms/#classes-PhysicalResource>.+dctPhysicalResource :: ScopedName+dctPhysicalResource = toDCT "PhysicalResource"++-- | @dcterms:Policy@ from <http://dublincore.org/documents/dcmi-terms/#classes-Policy>.+dctPolicy :: ScopedName+dctPolicy = toDCT "Policy"++-- | @dcterms:ProvenanceStatement@ from <http://dublincore.org/documents/dcmi-terms/#classes-ProvenanceStatement>.+dctProvenanceStatement :: ScopedName+dctProvenanceStatement = toDCT "ProvenanceStatement"++-- | @dcterms:RightsStatement@ from <http://dublincore.org/documents/dcmi-terms/#classes-RightsStatement>.+dctRightsStatement :: ScopedName+dctRightsStatement = toDCT "RightsStatement"++-- | @dcterms:SizeOrDuration@ from <http://dublincore.org/documents/dcmi-terms/#classes-SizeOrDuration>.+dctSizeOrDuration :: ScopedName+dctSizeOrDuration = toDCT "SizeOrDuration"++-- | @dcterms:Standard@ from <http://dublincore.org/documents/dcmi-terms/#classes-Standard>.+dctStandard :: ScopedName+dctStandard = toDCT "Standard"++-- Properties++-- | @dcterms:abstract@ from <http://dublincore.org/documents/dcmi-terms/#terms-abstract>. +dctabstract :: ScopedName+dctabstract = toDCT "abstract"++-- | @dcterms:accessRights@ from <http://dublincore.org/documents/dcmi-terms/#terms-accessRights>. +dctaccessRights :: ScopedName+dctaccessRights = toDCT "accessRights"++-- | @dcterms:accrualMethod@ from <http://dublincore.org/documents/dcmi-terms/#terms-accrualMethod>. +dctaccrualMethod :: ScopedName+dctaccrualMethod = toDCT "accrualMethod"++-- | @dcterms:accrualPeriodicity@ from <http://dublincore.org/documents/dcmi-terms/#terms-accrualPeriodicity>. +dctaccrualPeriodicity :: ScopedName+dctaccrualPeriodicity = toDCT "accrualPeriodicity"++-- | @dcterms:accrualPolicy@ from <http://dublincore.org/documents/dcmi-terms/#terms-accrualPolicy>. +dctaccrualPolicy :: ScopedName+dctaccrualPolicy = toDCT "accrualPolicy"++-- | @dcterms:alternative@ from <http://dublincore.org/documents/dcmi-terms/#terms-alternative>. +dctalternative :: ScopedName+dctalternative = toDCT "alternative"++-- | @dcterms:audience@ from <http://dublincore.org/documents/dcmi-terms/#terms-audience>. +dctaudience :: ScopedName+dctaudience = toDCT "audience"++-- | @dcterms:available@ from <http://dublincore.org/documents/dcmi-terms/#terms-available>. +dctavailable :: ScopedName+dctavailable = toDCT "available"++-- | @dcterms:bibliographicCitation@ from <http://dublincore.org/documents/dcmi-terms/#terms-bibliographicCitation>. +dctbibliographicCitation :: ScopedName+dctbibliographicCitation = toDCT "bibliographicCitation"++-- | @dcterms:conformsTo@ from <http://dublincore.org/documents/dcmi-terms/#terms-conformsTo>. +dctconformsTo :: ScopedName+dctconformsTo = toDCT "conformsTo"++-- | @dcterms:contributor@ from <http://dublincore.org/documents/dcmi-terms/#terms-contributor>. +dctcontributor :: ScopedName+dctcontributor = toDCT "contributor"++-- | @dcterms:coverage@ from <http://dublincore.org/documents/dcmi-terms/#terms-coverage>. +dctcoverage :: ScopedName+dctcoverage = toDCT "coverage"++-- | @dcterms:created@ from <http://dublincore.org/documents/dcmi-terms/#terms-created>. +dctcreated :: ScopedName+dctcreated = toDCT "created"++-- | @dcterms:creator@ from <http://dublincore.org/documents/dcmi-terms/#terms-creator>. +dctcreator :: ScopedName+dctcreator = toDCT "creator"++-- | @dcterms:date@ from <http://dublincore.org/documents/dcmi-terms/#terms-date>. +dctdate :: ScopedName+dctdate = toDCT "date"++-- | @dcterms:dateAccepted@ from <http://dublincore.org/documents/dcmi-terms/#terms-dateAccepted>. +dctdateAccepted :: ScopedName+dctdateAccepted = toDCT "dateAccepted"++-- | @dcterms:dateCopyrighted@ from <http://dublincore.org/documents/dcmi-terms/#terms-dateCopyrighted>. +dctdateCopyrighted :: ScopedName+dctdateCopyrighted = toDCT "dateCopyrighted"++-- | @dcterms:dateSubmitted@ from <http://dublincore.org/documents/dcmi-terms/#terms-dateSubmitted>. +dctdateSubmitted :: ScopedName+dctdateSubmitted = toDCT "dateSubmitted"++-- | @dcterms:description@ from <http://dublincore.org/documents/dcmi-terms/#terms-description>. +dctdescription :: ScopedName+dctdescription = toDCT "description"++-- | @dcterms:educationLevel@ from <http://dublincore.org/documents/dcmi-terms/#terms-educationLevel>. +dcteducationLevel :: ScopedName+dcteducationLevel = toDCT "educationLevel"++-- | @dcterms:extent@ from <http://dublincore.org/documents/dcmi-terms/#terms-extent>. +dctextent :: ScopedName+dctextent = toDCT "extent"++-- | @dcterms:format@ from <http://dublincore.org/documents/dcmi-terms/#terms-format>. +dctformat :: ScopedName+dctformat = toDCT "format"++-- | @dcterms:hasFormat@ from <http://dublincore.org/documents/dcmi-terms/#terms-hasFormat>. +dcthasFormat :: ScopedName+dcthasFormat = toDCT "hasFormat"++-- | @dcterms:hasPart@ from <http://dublincore.org/documents/dcmi-terms/#terms-hasPart>. +dcthasPart :: ScopedName+dcthasPart = toDCT "hasPart"++-- | @dcterms:hasVersion@ from <http://dublincore.org/documents/dcmi-terms/#terms-hasVersion>. +dcthasVersion :: ScopedName+dcthasVersion = toDCT "hasVersion"++-- | @dcterms:identifier@ from <http://dublincore.org/documents/dcmi-terms/#terms-identifier>. +dctidentifier :: ScopedName+dctidentifier = toDCT "identifier"++-- | @dcterms:instructionalMethod@ from <http://dublincore.org/documents/dcmi-terms/#terms-instructionalMethod>. +dctinstructionalMethod :: ScopedName+dctinstructionalMethod = toDCT "instructionalMethod"++-- | @dcterms:isFormatOf@ from <http://dublincore.org/documents/dcmi-terms/#terms-isFormatOf>. +dctisFormatOf :: ScopedName+dctisFormatOf = toDCT "isFormatOf"++-- | @dcterms:isPartOf@ from <http://dublincore.org/documents/dcmi-terms/#terms-isPartOf>. +dctisPartOf :: ScopedName+dctisPartOf = toDCT "isPartOf"++-- | @dcterms:isReferencedBy@ from <http://dublincore.org/documents/dcmi-terms/#terms-isReferencedBy>. +dctisReferencedBy :: ScopedName+dctisReferencedBy = toDCT "isReferencedBy"++-- | @dcterms:isReplacedBy@ from <http://dublincore.org/documents/dcmi-terms/#terms-isReplacedBy>. +dctisReplacedBy :: ScopedName+dctisReplacedBy = toDCT "isReplacedBy"++-- | @dcterms:isRequiredBy@ from <http://dublincore.org/documents/dcmi-terms/#terms-isRequiredBy>. +dctisRequiredBy :: ScopedName+dctisRequiredBy = toDCT "isRequiredBy"++-- | @dcterms:issued@ from <http://dublincore.org/documents/dcmi-terms/#terms-issued>. +dctissued :: ScopedName+dctissued = toDCT "issued"++-- | @dcterms:isVersionOf@ from <http://dublincore.org/documents/dcmi-terms/#terms-isVersionOf>. +dctisVersionOf :: ScopedName+dctisVersionOf = toDCT "isVersionOf"++-- | @dcterms:language@ from <http://dublincore.org/documents/dcmi-terms/#terms-language>. +dctlanguage :: ScopedName+dctlanguage = toDCT "language"++-- | @dcterms:license@ from <http://dublincore.org/documents/dcmi-terms/#terms-license>. +dctlicense :: ScopedName+dctlicense = toDCT "license"++-- | @dcterms:mediator@ from <http://dublincore.org/documents/dcmi-terms/#terms-mediator>. +dctmediator :: ScopedName+dctmediator = toDCT "mediator"++-- | @dcterms:medium@ from <http://dublincore.org/documents/dcmi-terms/#terms-medium>. +dctmedium :: ScopedName+dctmedium = toDCT "medium"++-- | @dcterms:modified@ from <http://dublincore.org/documents/dcmi-terms/#terms-modified>. +dctmodified :: ScopedName+dctmodified = toDCT "modified"++-- | @dcterms:provenance@ from <http://dublincore.org/documents/dcmi-terms/#terms-provenance>. +dctprovenance :: ScopedName+dctprovenance = toDCT "provenance"++-- | @dcterms:publisher@ from <http://dublincore.org/documents/dcmi-terms/#terms-publisher>. +dctpublisher :: ScopedName+dctpublisher = toDCT "publisher"++-- | @dcterms:references@ from <http://dublincore.org/documents/dcmi-terms/#terms-references>. +dctreferences :: ScopedName+dctreferences = toDCT "references"++-- | @dcterms:relation@ from <http://dublincore.org/documents/dcmi-terms/#terms-relation>. +dctrelation :: ScopedName+dctrelation = toDCT "relation"++-- | @dcterms:replaces@ from <http://dublincore.org/documents/dcmi-terms/#terms-replaces>. +dctreplaces :: ScopedName+dctreplaces = toDCT "replaces"++-- | @dcterms:requires@ from <http://dublincore.org/documents/dcmi-terms/#terms-requires>. +dctrequires :: ScopedName+dctrequires = toDCT "requires"++-- | @dcterms:rights@ from <http://dublincore.org/documents/dcmi-terms/#terms-rights>. +dctrights :: ScopedName+dctrights = toDCT "rights"++-- | @dcterms:rightsHolder@ from <http://dublincore.org/documents/dcmi-terms/#terms-rightsHolder>. +dctrightsHolder :: ScopedName+dctrightsHolder = toDCT "rightsHolder"++-- | @dcterms:source@ from <http://dublincore.org/documents/dcmi-terms/#terms-source>. +dctsource :: ScopedName+dctsource = toDCT "source"++-- | @dcterms:spatial@ from <http://dublincore.org/documents/dcmi-terms/#terms-spatial>. +dctspatial :: ScopedName+dctspatial = toDCT "spatial"++-- | @dcterms:subject@ from <http://dublincore.org/documents/dcmi-terms/#terms-subject>. +dctsubject :: ScopedName+dctsubject = toDCT "subject"++-- | @dcterms:tableOfContents@ from <http://dublincore.org/documents/dcmi-terms/#terms-tableOfContents>. +dcttableOfContents :: ScopedName+dcttableOfContents = toDCT "tableOfContents"++-- | @dcterms:temporal@ from <http://dublincore.org/documents/dcmi-terms/#terms-temporal>. +dcttemporal :: ScopedName+dcttemporal = toDCT "temporal"++-- | @dcterms:title@ from <http://dublincore.org/documents/dcmi-terms/#terms-title>. +dcttitle :: ScopedName+dcttitle = toDCT "title"++-- | @dcterms:type@ from <http://dublincore.org/documents/dcmi-terms/#terms-type>. +dcttype :: ScopedName+dcttype = toDCT "type"++-- | @dcterms:valid@ from <http://dublincore.org/documents/dcmi-terms/#terms-valid>. +dctvalid :: ScopedName+dctvalid = toDCT "valid"++-- legacy elements vocabulary: properties++-- | @dcelem:contributor@ from <http://dublincore.org/documents/dcmi-terms/#elements-contributor>.+dcelemcontributor :: ScopedName+dcelemcontributor = toDCE "contributor"++-- | @dcelem:coverage@ from <http://dublincore.org/documents/dcmi-terms/#elements-coverage>.+dcelemcoverage :: ScopedName+dcelemcoverage = toDCE "coverage"++-- | @dcelem:creator@ from <http://dublincore.org/documents/dcmi-terms/#elements-creator>.+dcelemcreator :: ScopedName+dcelemcreator = toDCE "creator"++-- | @dcelem:date@ from <http://dublincore.org/documents/dcmi-terms/#elements-date>.+dcelemdate :: ScopedName+dcelemdate = toDCE "date"++-- | @dcelem:description@ from <http://dublincore.org/documents/dcmi-terms/#elements-description>.+dcelemdescription :: ScopedName+dcelemdescription = toDCE "description"++-- | @dcelem:format@ from <http://dublincore.org/documents/dcmi-terms/#elements-format>.+dcelemformat :: ScopedName+dcelemformat = toDCE "format"++-- | @dcelem:identifier@ from <http://dublincore.org/documents/dcmi-terms/#elements-identifier>.+dcelemidentifier :: ScopedName+dcelemidentifier = toDCE "identifier"++-- | @dcelem:language@ from <http://dublincore.org/documents/dcmi-terms/#elements-language>.+dcelemlanguage :: ScopedName+dcelemlanguage = toDCE "language"++-- | @dcelem:publisher@ from <http://dublincore.org/documents/dcmi-terms/#elements-publisher>.+dcelempublisher :: ScopedName+dcelempublisher = toDCE "publisher"++-- | @dcelem:relation@ from <http://dublincore.org/documents/dcmi-terms/#elements-relation>.+dcelemrelation :: ScopedName+dcelemrelation = toDCE "relation"++-- | @dcelem:rights@ from <http://dublincore.org/documents/dcmi-terms/#elements-rights>.+dcelemrights :: ScopedName+dcelemrights = toDCE "rights"++-- | @dcelem:source@ from <http://dublincore.org/documents/dcmi-terms/#elements-source>.+dcelemsource :: ScopedName+dcelemsource = toDCE "source"++-- | @dcelem:subject@ from <http://dublincore.org/documents/dcmi-terms/#elements-subject>.+dcelemsubject :: ScopedName+dcelemsubject = toDCE "subject"++-- | @dcelem:title@ from <http://dublincore.org/documents/dcmi-terms/#elements-title>.+dcelemtitle :: ScopedName+dcelemtitle = toDCE "title"++-- | @dcelem:type@ from <http://dublincore.org/documents/dcmi-terms/#elements-type>.+dcelemtype :: ScopedName+dcelemtype = toDCE "type"++-- Datatypes++-- | @dcterms:Box@ from <http://dublincore.org/documents/dcmi-terms/#ses-Box>.+dctBox :: ScopedName+dctBox = toDCT "Box"++-- | @dcterms:ISO3166@ from <http://dublincore.org/documents/dcmi-terms/#ses-ISO3166>.+dctISO3166 :: ScopedName+dctISO3166 = toDCT "ISO3166"++-- | @dcterms:ISO639-2@ from <http://dublincore.org/documents/dcmi-terms/#ses-ISO639-2>.+dctISO639_2 :: ScopedName+dctISO639_2 = toDCT "ISO639-2"++-- | @dcterms:ISO639-3@ from <http://dublincore.org/documents/dcmi-terms/#ses-ISO639-3>.+dctISO639_3 :: ScopedName+dctISO639_3 = toDCT "ISO639-3"++-- | @dcterms:Period@ from <http://dublincore.org/documents/dcmi-terms/#ses-Period>.+dctPeriod :: ScopedName+dctPeriod = toDCT "Period"++-- | @dcterms:Point@ from <http://dublincore.org/documents/dcmi-terms/#ses-Point>.+dctPoint :: ScopedName+dctPoint = toDCT "Point"++-- | @dcterms:RFC1766@ from <http://dublincore.org/documents/dcmi-terms/#ses-RFC1766>.+dctRFC1766 :: ScopedName+dctRFC1766 = toDCT "RFC1766"++-- | @dcterms:RFC3066@ from <http://dublincore.org/documents/dcmi-terms/#ses-RFC3066>.+dctRFC3066 :: ScopedName+dctRFC3066 = toDCT "RFC3066"++-- | @dcterms:RFC4646@ from <http://dublincore.org/documents/dcmi-terms/#ses-RFC4646>.+dctRFC4646 :: ScopedName+dctRFC4646 = toDCT "RFC4646"++-- | @dcterms:RFC5646@ from <http://dublincore.org/documents/dcmi-terms/#ses-RFC5646>.+dctRFC5646 :: ScopedName+dctRFC5646 = toDCT "RFC5646"++-- | @dcterms:URI@ from <http://dublincore.org/documents/dcmi-terms/#ses-URI>.+dctURI :: ScopedName+dctURI = toDCT "URI"++-- | @dcterms:W3CDTF@ from <http://dublincore.org/documents/dcmi-terms/#ses-W3CDTF>.+dctW3CDTF :: ScopedName+dctW3CDTF = toDCT "W3CDTF"++-- | @dcam:memberOf@ from <http://dublincore.org/documents/dcmi-terms/#dcam-memberOf>.+dcammemberOf :: ScopedName+dcammemberOf = toDCAM "memberOf"++-- | @dcam:memberOf@ from <http://dublincore.org/documents/dcmi-terms/#dcam-memberOf>.+dcamVocabularyEncodingScheme :: ScopedName+dcamVocabularyEncodingScheme = toDCAM "VocabularyEncodingScheme"++-- | @dctype:Collection@ from <http://dublincore.org/documents/dcmi-terms/#dcmitype-Collection>.+dctypeCollection :: ScopedName+dctypeCollection = toDCTYPE "Collection"++-- | @dctype:Dataset@ from <http://dublincore.org/documents/dcmi-terms/#dcmitype-Dataset>.+dctypeDataset :: ScopedName+dctypeDataset = toDCTYPE "Dataset"++-- | @dctype:Event@ from <http://dublincore.org/documents/dcmi-terms/#dcmitype-Event>.+dctypeEvent :: ScopedName+dctypeEvent = toDCTYPE "Event"++-- | @dctype:Image@ from <http://dublincore.org/documents/dcmi-terms/#dcmitype-Image>.+dctypeImage :: ScopedName+dctypeImage = toDCTYPE "Image"++-- | @dctype:InteractiveResource@ from <http://dublincore.org/documents/dcmi-terms/#dcmitype-InteractiveResource>.+dctypeInteractiveResource :: ScopedName+dctypeInteractiveResource = toDCTYPE "InteractiveResource"++-- | @dctype:Service@ from <http://dublincore.org/documents/dcmi-terms/#dcmitype-Service>.+dctypeService :: ScopedName+dctypeService = toDCTYPE "Service"++-- | @dctype:Software@ from <http://dublincore.org/documents/dcmi-terms/#dcmitype-Software>.+dctypeSoftware :: ScopedName+dctypeSoftware = toDCTYPE "Software"++-- | @dctype:Sound@ from <http://dublincore.org/documents/dcmi-terms/#dcmitype-Sound>.+dctypeSound :: ScopedName+dctypeSound = toDCTYPE "Sound"++-- | @dctype:Text@ from <http://dublincore.org/documents/dcmi-terms/#dcmitype-Text>.+dctypeText :: ScopedName+dctypeText = toDCTYPE "Text"++-- | @dctype:PhysicalObject@ from <http://dublincore.org/documents/dcmi-terms/#dcmitype-PhysicalObject>.+dctypePhysicalObject :: ScopedName+dctypePhysicalObject = toDCTYPE "PhysicalObject"++-- | @dctype:StillImage@ from <http://dublincore.org/documents/dcmi-terms/#dcmitype-StillImage>.+dctypeStillImage :: ScopedName+dctypeStillImage = toDCTYPE "StillImage"++-- | @dctype:MovingImage@ from <http://dublincore.org/documents/dcmi-terms/#dcmitype-MovingImage>.+dctypeMovingImage :: ScopedName+dctypeMovingImage = toDCTYPE "MovingImage"++-- | @dcterms:LCSH@ from <http://dublincore.org/documents/dcmi-terms/#ves-LCSH>.+dctLCSH :: ScopedName+dctLCSH = toDCT "LCSH"++-- | @dcterms:MESH@ from <http://dublincore.org/documents/dcmi-terms/#ves-MESH>.+dctMESH :: ScopedName+dctMESH = toDCT "MESH"++-- | @dcterms:DDC@ from <http://dublincore.org/documents/dcmi-terms/#ves-DDC>.+dctDDC :: ScopedName+dctDDC = toDCT "DDC"++-- | @dcterms:LCC@ from <http://dublincore.org/documents/dcmi-terms/#ves-LCC>.+dctLCC :: ScopedName+dctLCC = toDCT "LCC"++-- | @dcterms:UDC@ from <http://dublincore.org/documents/dcmi-terms/#ves-UDC>.+dctUDC :: ScopedName+dctUDC = toDCT "UDC"++-- | @dcterms:DCMIType@ from <http://dublincore.org/documents/dcmi-terms/#ves-DCMIType>.+dctDCMIType :: ScopedName+dctDCMIType = toDCT "DCMIType"++-- | @dcterms:IMT@ from <http://dublincore.org/documents/dcmi-terms/#ves-IMT>.+dctIMT :: ScopedName+dctIMT = toDCT "IMT"++-- | @dcterms:TGN@ from <http://dublincore.org/documents/dcmi-terms/#ves-TGN>.+dctTGN :: ScopedName+dctTGN = toDCT "TGN"++-- | @dcterms:NLM@ from <http://dublincore.org/documents/dcmi-terms/#ves-NLM>.+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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/Vocabulary/FOAF.hs view
@@ -0,0 +1,466 @@+{-# 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 (<http://xmlns.com/foaf/spec/>).+--+-- 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/\",+ -- <http://xmlns.com/foaf/spec/20100809.html>.+ 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.Utils.Namespace (Namespace, makeNamespace, ScopedName, makeNSScopedName)++import Data.Maybe (fromMaybe)+import Network.URI (URI, parseURI)++import qualified Data.Text as T++------------------------------------------------------------+-- Namespace+------------------------------------------------------------++foafURI :: URI+foafURI = fromMaybe (error "Internal error processing FOAF URI") $ parseURI "http://xmlns.com/foaf/0.1/"++-- | Maps @foaf@ to <http://xmlns.com/foaf/0.1/>.+namespaceFOAF :: Namespace+namespaceFOAF = makeNamespace (Just "foaf") foafURI++------------------------------------------------------------+-- Terms+------------------------------------------------------------++toF :: T.Text -> ScopedName+toF = makeNSScopedName namespaceFOAF++-- Classes++-- | @foaf:Agent@ from <http://xmlns.com/foaf/spec/#term_Agent>.+foafAgent :: ScopedName+foafAgent = toF "Agent"++-- | @foaf:Document@ from <http://xmlns.com/foaf/spec/#term_Document>.+foafDocument :: ScopedName+foafDocument = toF "Document"++-- | @foaf:Group@ from <http://xmlns.com/foaf/spec/#term_Group>.+foafGroup :: ScopedName+foafGroup = toF "Group"++-- | @foaf:Image@ from <http://xmlns.com/foaf/spec/#term_Image>.+foafImage :: ScopedName+foafImage = toF "Image"++-- | @foaf:LabelProperty@ from <http://xmlns.com/foaf/spec/#term_LabelProperty>.+foafLabelProperty :: ScopedName+foafLabelProperty = toF "LabelProperty"++-- | @foaf:OnlineAccount@ from <http://xmlns.com/foaf/spec/#term_OnlineAccount>.+foafOnlineAccount :: ScopedName+foafOnlineAccount = toF "OnlineAccount"++-- | @foaf:OnlineChatAccount@ from <http://xmlns.com/foaf/spec/#term_OnlineChatAccount>.+foafOnlineChatAccount :: ScopedName+foafOnlineChatAccount = toF "OnlineChatAccount"++-- | @foaf:OnlineEcommerceAccount@ from <http://xmlns.com/foaf/spec/#term_OnlineEcommerceAccount>.+foafOnlineEcommerceAccount :: ScopedName+foafOnlineEcommerceAccount = toF "OnlineEcommerceAccount"++-- | @foaf:OnlineGamingAccount@ from <http://xmlns.com/foaf/spec/#term_OnlineGamingAccount>.+foafOnlineGamingAccount :: ScopedName+foafOnlineGamingAccount = toF "OnlineGamingAccount"++-- | @foaf:Organization@ from <http://xmlns.com/foaf/spec/#term_Organization>.+foafOrganization :: ScopedName+foafOrganization = toF "Organization"++-- | @foaf:Person@ from <http://xmlns.com/foaf/spec/#term_Person>.+foafPerson :: ScopedName+foafPerson = toF "Person"++-- | @foaf:PersonalProfileDocument@ from <http://xmlns.com/foaf/spec/#term_PersonalProfileDocument>.+foafPersonalProfileDocument :: ScopedName+foafPersonalProfileDocument = toF "PersonalProfileDocument"++-- | @foaf:Project@ from <http://xmlns.com/foaf/spec/#term_Project>.+foafProject :: ScopedName+foafProject = toF "Project"++-- Properties++-- | @foaf:account@ from <http://xmlns.com/foaf/spec/#term_account>. +foafaccount :: ScopedName+foafaccount = toF "account"+ +-- | @foaf:accountName@ from <http://xmlns.com/foaf/spec/#term_accountName>. +foafaccountName :: ScopedName+foafaccountName = toF "accountName"+ +-- | @foaf:accountServiceHomepage@ from <http://xmlns.com/foaf/spec/#term_accountServiceHomepage>. +foafaccountServiceHomepage :: ScopedName+foafaccountServiceHomepage = toF "accountServiceHomepage"+ +-- | @foaf:age@ from <http://xmlns.com/foaf/spec/#term_age>. +foafage :: ScopedName+foafage = toF "age"+ +-- | @foaf:aimChatID@ from <http://xmlns.com/foaf/spec/#term_aimChatID>. +foafaimChatID :: ScopedName+foafaimChatID = toF "aimChatID"+ +-- | @foaf:based_near@ from <http://xmlns.com/foaf/spec/#term_based_near>. +foafbased_near :: ScopedName+foafbased_near = toF "based_near"+ +-- | @foaf:birthday@ from <http://xmlns.com/foaf/spec/#term_birthday>. +foafbirthday :: ScopedName+foafbirthday = toF "birthday"+ +-- | @foaf:currentProject@ from <http://xmlns.com/foaf/spec/#term_currentProject>. +foafcurrentProject :: ScopedName+foafcurrentProject = toF "currentProject"+ +-- | @foaf:depiction@ from <http://xmlns.com/foaf/spec/#term_depiction>. +foafdepiction :: ScopedName+foafdepiction = toF "depiction"+ +-- | @foaf:depicts@ from <http://xmlns.com/foaf/spec/#term_depicts>. +foafdepicts :: ScopedName+foafdepicts = toF "depicts"+ +-- | @foaf:dnaChecksum@ from <http://xmlns.com/foaf/spec/#term_dnaChecksum>. +foafdnaChecksum :: ScopedName+foafdnaChecksum = toF "dnaChecksum"+ +-- | @foaf:familyName@ from <http://xmlns.com/foaf/spec/#term_familyName>. +foaffamilyName :: ScopedName+foaffamilyName = toF "familyName"+ +-- | @foaf:family_name@ from <http://xmlns.com/foaf/spec/#term_family_name>. +foaffamily_name :: ScopedName+foaffamily_name = toF "family_name"+ +-- | @foaf:firstName@ from <http://xmlns.com/foaf/spec/#term_firstName>. +foaffirstName :: ScopedName+foaffirstName = toF "firstName"+ +-- | @foaf:focus@ from <http://xmlns.com/foaf/spec/#term_focus>. +foaffocus :: ScopedName+foaffocus = toF "focus"+ +-- | @foaf:fundedBy@ from <http://xmlns.com/foaf/spec/#term_fundedBy>. +foaffundedBy :: ScopedName+foaffundedBy = toF "fundedBy"+ +-- | @foaf:geekcode@ from <http://xmlns.com/foaf/spec/#term_geekcode>. +foafgeekcode :: ScopedName+foafgeekcode = toF "geekcode"+ +-- | @foaf:gender@ from <http://xmlns.com/foaf/spec/#term_gender>. +foafgender :: ScopedName+foafgender = toF "gender"+ +-- | @foaf:givenName@ from <http://xmlns.com/foaf/spec/#term_givenName>. +foafgivenName :: ScopedName+foafgivenName = toF "givenName"+ +-- | @foaf:givenname@ from <http://xmlns.com/foaf/spec/#term_givenname>. +foafgivenname :: ScopedName+foafgivenname = toF "givenname"+ +-- | @foaf:holdsAccount@ from <http://xmlns.com/foaf/spec/#term_holdsAccount>. +foafholdsAccount :: ScopedName+foafholdsAccount = toF "holdsAccount"+ +-- | @foaf:homepage@ from <http://xmlns.com/foaf/spec/#term_homepage>. +foafhomepage :: ScopedName+foafhomepage = toF "homepage"+ +-- | @foaf:icqChatID@ from <http://xmlns.com/foaf/spec/#term_icqChatID>. +foaficqChatID :: ScopedName+foaficqChatID = toF "icqChatID"+ +-- | @foaf:img@ from <http://xmlns.com/foaf/spec/#term_img>. +foafimg :: ScopedName+foafimg = toF "img"+ +-- | @foaf:interest@ from <http://xmlns.com/foaf/spec/#term_interest>. +foafinterest :: ScopedName+foafinterest = toF "interest"+ +-- | @foaf:isPrimaryTopicOf@ from <http://xmlns.com/foaf/spec/#term_isPrimaryTopicOf>. +foafisPrimaryTopicOf :: ScopedName+foafisPrimaryTopicOf = toF "isPrimaryTopicOf"+ +-- | @foaf:jabberID@ from <http://xmlns.com/foaf/spec/#term_jabberID>. +foafjabberID :: ScopedName+foafjabberID = toF "jabberID"+ +-- | @foaf:knows@ from <http://xmlns.com/foaf/spec/#term_knows>. +foafknows :: ScopedName+foafknows = toF "knows"+ +-- | @foaf:lastName@ from <http://xmlns.com/foaf/spec/#term_lastName>. +foaflastName :: ScopedName+foaflastName = toF "lastName"+ +-- | @foaf:logo@ from <http://xmlns.com/foaf/spec/#term_logo>. +foaflogo :: ScopedName+foaflogo = toF "logo"+ +-- | @foaf:made@ from <http://xmlns.com/foaf/spec/#term_made>. +foafmade :: ScopedName+foafmade = toF "made"+ +-- | @foaf:maker@ from <http://xmlns.com/foaf/spec/#term_maker>. +foafmaker :: ScopedName+foafmaker = toF "maker"+ +-- | @foaf:mbox@ from <http://xmlns.com/foaf/spec/#term_mbox>. +foafmbox :: ScopedName+foafmbox = toF "mbox"+ +-- | @foaf:mbox_sha1sum@ from <http://xmlns.com/foaf/spec/#term_mbox_sha1sum>. +foafmbox_sha1sum :: ScopedName+foafmbox_sha1sum = toF "mbox_sha1sum"+ +-- | @foaf:member@ from <http://xmlns.com/foaf/spec/#term_member>. +foafmember :: ScopedName+foafmember = toF "member"+ +-- | @foaf:membershipClass@ from <http://xmlns.com/foaf/spec/#term_membershipClass>. +foafmembershipClass :: ScopedName+foafmembershipClass = toF "membershipClass"++-- | @foaf:msnChatID@ from <http://xmlns.com/foaf/spec/#term_msnChatID>. +foafmsnChatID :: ScopedName+foafmsnChatID = toF "msnChatID"++-- | @foaf:myersBriggs@ from <http://xmlns.com/foaf/spec/#term_myersBriggs>. +foafmyersBriggs :: ScopedName+foafmyersBriggs = toF "myersBriggs"++-- | @foaf:name@ from <http://xmlns.com/foaf/spec/#term_name>. +foafname :: ScopedName+foafname = toF "name"++-- | @foaf:nick@ from <http://xmlns.com/foaf/spec/#term_nick>. +foafnick :: ScopedName+foafnick = toF "nick"++-- | @foaf:openid@ from <http://xmlns.com/foaf/spec/#term_openid>. +foafopenid :: ScopedName+foafopenid = toF "openid"+ +-- | @foaf:page@ from <http://xmlns.com/foaf/spec/#term_page>. +foafpage :: ScopedName+foafpage = toF "page"+ +-- | @foaf:pastProject@ from <http://xmlns.com/foaf/spec/#term_pastProject>. +foafpastProject :: ScopedName+foafpastProject = toF "pastProject"+ +-- | @foaf:phone@ from <http://xmlns.com/foaf/spec/#term_phone>. +foafphone :: ScopedName+foafphone = toF "phone"+ +-- | @foaf:plan@ from <http://xmlns.com/foaf/spec/#term_plan>. +foafplan :: ScopedName+foafplan = toF "plan"++-- | @foaf:primaryTopic@ from <http://xmlns.com/foaf/spec/#term_primaryTopic>. +foafprimaryTopic :: ScopedName+foafprimaryTopic = toF "primaryTopic"++-- | @foaf:publications@ from <http://xmlns.com/foaf/spec/#term_publications>. +foafpublications :: ScopedName+foafpublications = toF "publications"++-- | @foaf:schoolHomepage@ from <http://xmlns.com/foaf/spec/#term_schoolHomepage>. +foafschoolHomepage :: ScopedName+foafschoolHomepage = toF "schoolHomepage"++-- | @foaf:sha1@ from <http://xmlns.com/foaf/spec/#term_sha1>. +foafsha1 :: ScopedName+foafsha1 = toF "sha1"++-- | @foaf:skypeID@ from <http://xmlns.com/foaf/spec/#term_skypeID>. +foafskypeID :: ScopedName+foafskypeID = toF "skypeID"+ +-- | @foaf:status@ from <http://xmlns.com/foaf/spec/#term_status>. +foafstatus :: ScopedName+foafstatus = toF "status"+ +-- | @foaf:surname@ from <http://xmlns.com/foaf/spec/#term_surname>. +foafsurname :: ScopedName+foafsurname = toF "surname"+ +-- | @foaf:theme@ from <http://xmlns.com/foaf/spec/#term_theme>. +foaftheme :: ScopedName+foaftheme = toF "theme"+ +-- | @foaf:thumbnail@ from <http://xmlns.com/foaf/spec/#term_thumbnail>. +foafthumbnail :: ScopedName+foafthumbnail = toF "thumbnail"++-- | @foaf:tipjar@ from <http://xmlns.com/foaf/spec/#term_tipjar>. +foaftipjar :: ScopedName+foaftipjar = toF "tipjar"++-- | @foaf:title@ from <http://xmlns.com/foaf/spec/#term_title>. +foaftitle :: ScopedName+foaftitle = toF "title"++-- | @foaf:topic@ from <http://xmlns.com/foaf/spec/#term_topic>. +foaftopic :: ScopedName+foaftopic = toF "topic"+ +-- | @foaf:topic_interest@ from <http://xmlns.com/foaf/spec/#term_topic_interest>. +foaftopic_interest :: ScopedName+foaftopic_interest = toF "topic_interest"+ +-- | @foaf:weblog@ from <http://xmlns.com/foaf/spec/#term_weblog>. +foafweblog :: ScopedName+foafweblog = toF "weblog"+ +-- | @foaf:workInfoHomepage@ from <http://xmlns.com/foaf/spec/#term_workInfoHomepage>. +foafworkInfoHomepage :: ScopedName+foafworkInfoHomepage = toF "workInfoHomepage"+ +-- | @foaf:workplaceHomepage@ from <http://xmlns.com/foaf/spec/#term_workplaceHomepage>. +foafworkplaceHomepage :: ScopedName+foafworkplaceHomepage = toF "workplaceHomepage"+ +-- | @foaf:yahooChatID@ from <http://xmlns.com/foaf/spec/#term_yahooChatID>. +foafyahooChatID :: ScopedName+foafyahooChatID = toF "yahooChatID"+ +--------------------------------------------------------------------------------+--+-- 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/Vocabulary/Geo.hs view
@@ -0,0 +1,109 @@+{-# LANGUAGE OverloadedStrings #-}++--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : Swish.RDF.Vocabulary.Geo+-- 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 Geo+-- vocabulary (<http://www.w3.org/2003/01/geo/>, WGS84 Geo Positioning: an RDF vocabulary).+--+-- Note that unlike some of the existing vocabularies in Swish, the Geo+-- one matches the case and spelling of the RDF terms; so we+-- use 'geolat' rather than @geoLat@.+--+--------------------------------------------------------------------------------++module Swish.RDF.Vocabulary.Geo+ ( + namespaceGEO+ -- * Classes+ , geoSpatialThing+ , geoPoint+ -- * Properties+ , geolocation+ , geolat+ , geolong+ , geolat_long+ )+where++import Swish.Utils.Namespace (Namespace, makeNamespace, ScopedName, makeNSScopedName)++import Data.Maybe (fromMaybe)+import Network.URI (URI, parseURI)++import qualified Data.Text as T++------------------------------------------------------------+-- Namespace+------------------------------------------------------------++geoURI :: URI+geoURI = fromMaybe (error "Internal error processing Geo URI") $ parseURI "http://www.w3.org/2003/01/geo/wgs84_pos#"++-- | Maps @geo@ to <http://www.w3.org/2003/01/geo/wgs84_pos#>.+namespaceGEO :: Namespace+namespaceGEO = makeNamespace (Just "geo") geoURI++------------------------------------------------------------+-- Terms+------------------------------------------------------------++toG :: T.Text -> ScopedName+toG = makeNSScopedName namespaceGEO++-- | @geo:location@.+geolocation :: ScopedName+geolocation = toG "location"++-- | @geo:lat@.+geolat :: ScopedName+geolat = toG "lat"++-- | @geo:long@.+geolong :: ScopedName+geolong = toG "long"++-- | @geo:lat_long@ (it is suggested that this not be used when generating RDF statements).+geolat_long :: ScopedName+geolat_long = toG "lat_long"++-- | @geo:SpatialThing@.+geoSpatialThing :: ScopedName+geoSpatialThing = toG "SpatialThing"++-- | @geo:Point@.+geoPoint :: ScopedName+geoPoint = toG "Point"++--------------------------------------------------------------------------------+--+-- 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/Vocabulary/OWL.hs view
@@ -0,0 +1,175 @@+{-# LANGUAGE OverloadedStrings #-}++--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : Swish.RDF.Vocabulary.OWL+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : OverloadedStrings+--+-- This module defines vocabulary terms from the OWL vocabulary. Note that there+-- is an unfortunate mixture of styles for property names - e.g. 'owlSameAs'+-- and 'owlequivalentClass'. At present there is no systematic attempt to+-- include terms from the vocabulary.+--+--------------------------------------------------------------------------------++module Swish.RDF.Vocabulary.OWL+ ( + namespaceOWL+ + , owlOntology+ , owlimports+ , owlversionInfo+ , owldeprecated+ , owlpriorVersion+ , owlbackwardCompatibleWith+ , owlincompatibleWith+ + , owlClass+ , owlThing+ , owlNothing+ , owlNamedIndividual+ + , owlSameAs+ , owlequivalentClass+ , owlequivalentProperty+ + , owlObjectProperty+ , owlDatatypeProperty+ , owlAnnotationProperty+ + , owlrational+ , owlreal+ )+where++import Swish.Utils.Namespace (Namespace, makeNamespace, ScopedName, makeNSScopedName)++import Data.Maybe (fromMaybe)+import Network.URI (URI, parseURI)++import qualified Data.Text as T++------------------------------------------------------------+-- Namespace+------------------------------------------------------------++owlURI :: URI+owlURI = fromMaybe (error "Internal error processing OWL URI") $ parseURI "http://www.w3.org/2002/07/owl#"++-- | Maps @owl@ to <http://www.w3.org/2002/07/owl#>.+namespaceOWL :: Namespace+namespaceOWL = makeNamespace (Just "owl") owlURI++------------------------------------------------------------+-- Terms+------------------------------------------------------------++toO :: T.Text -> ScopedName+toO = makeNSScopedName namespaceOWL++-- | @owl:sameAs@.+owlSameAs :: ScopedName+owlSameAs = toO "sameAs"++-- | @owl:equivalentClass@.+owlequivalentClass :: ScopedName+owlequivalentClass = toO "equivalentClass"++-- | @owl:equivalentProperty@.+owlequivalentProperty :: ScopedName+owlequivalentProperty = toO "equivalentPropery"++-- | @owl:Ontology@.+owlOntology :: ScopedName+owlOntology = toO "Ontology"++-- | @owl:imports@.+owlimports :: ScopedName+owlimports = toO "imports"++-- | @owl:versionInfo@.+owlversionInfo :: ScopedName+owlversionInfo = toO "versionInfo"++-- | @owl:deprecated@.+owldeprecated :: ScopedName+owldeprecated = toO "deprecated"++-- | @owl:priorVersion@.+owlpriorVersion :: ScopedName+owlpriorVersion = toO "priorVersion"++-- | @owl:backwartCompatibleWith@.+owlbackwardCompatibleWith :: ScopedName+owlbackwardCompatibleWith = toO "backwardCompatibleWith"++-- | @owl:incompatibleWith@.+owlincompatibleWith :: ScopedName+owlincompatibleWith = toO "incompatibleWith"++-- | @owl:Class@.+owlClass :: ScopedName+owlClass = toO "Class"++-- | @owl:ObjectProperty@.+owlObjectProperty :: ScopedName+owlObjectProperty = toO "ObjectProperty"++-- | @owl:DatatypeProperty@.+owlDatatypeProperty :: ScopedName+owlDatatypeProperty = toO "DatatypeProperty"++-- | @owl:AnnotationProperty@.+owlAnnotationProperty :: ScopedName+owlAnnotationProperty = toO "AnnotationProperty"++-- | @owl:NamedIndividual@.+owlNamedIndividual :: ScopedName+owlNamedIndividual = toO "NamedIndividual"++-- | @owl:Thing@.+owlThing :: ScopedName+owlThing = toO "Thing"++-- | @owl:Thing@.+owlNothing :: ScopedName+owlNothing = toO "Nothing"++-- | @owl:rational@.+owlrational :: ScopedName+owlrational = toO "rational"++-- | @owl:real@.+owlreal :: ScopedName+owlreal = toO "real"++--------------------------------------------------------------------------------+--+-- 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/Vocabulary/RDF.hs view
@@ -0,0 +1,325 @@+{-# LANGUAGE OverloadedStrings #-}++--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : Swish.RDF.Vocabulary.RDF+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : OverloadedStrings+--+-- This module defines some commonly used vocabulary terms from the+-- RDF (<http://www.w3.org/TR/rdf-syntax-grammar/>) and+-- RDF Schema (<http://www.w3.org/TR/rdf-schema/>) documents.+--+--------------------------------------------------------------------------------++module Swish.RDF.Vocabulary.RDF+ ( + -- * Namespaces+ + namespaceRDF+ , namespaceRDFS+ + -- * RDF terms + -- + -- | These terms are described in <http://www.w3.org/TR/rdf-syntax-grammar/>; + -- the version used is \"W3C Recommendation 10 February 2004\", <http://www.w3.org/TR/2004/REC-rdf-syntax-grammar-20040210/>.+ -- + -- Some terms are listed within the RDF Schema terms below since their definition + -- is given within the RDF Schema document. + -- + , rdfRDF + , rdfDescription + , rdfID+ , rdfAbout+ , rdfParseType+ , rdfResource+ , rdfLi+ , rdfNodeID+ , rdfDatatype+ , rdf1, rdf2, rdfn+ -- * RDF Schema terms+ --+ -- | These are defined by <http://www.w3.org/TR/rdf-schema/>; the version+ -- used is \"W3C Recommendation 10 February 2004\", <http://www.w3.org/TR/2004/REC-rdf-schema-20040210/>.+ + -- ** Classes+ --+ -- | See the \"Classes\" section at <http://www.w3.org/TR/rdf-schema/#ch_classes> for more information.+ , rdfsResource+ , rdfsClass+ , rdfsLiteral+ , rdfsDatatype+ , rdfXMLLiteral+ , rdfProperty+ + -- ** Properties+ -- + -- | See the \"Properties\" section at <http://www.w3.org/TR/rdf-schema/#ch_classes> for more information.+ , rdfsRange+ , rdfsDomain+ , rdfType+ , rdfsSubClassOf+ , rdfsSubPropertyOf+ , rdfsLabel+ , rdfsComment+ -- ** Containers+ --+ -- | See the \"Container Classes and Properties\" section at <http://www.w3.org/TR/rdf-schema/#ch_containervocab>.+ , rdfsContainer+ , rdfBag+ , rdfSeq + , rdfAlt + , rdfsContainerMembershipProperty+ , rdfsMember+ -- ** Collections+ --+ -- | See the \"Collections\" section at <http://www.w3.org/TR/rdf-schema/#ch_collectionvocab>.+ , rdfList + , rdfFirst+ , rdfRest + , rdfNil + -- ** Reification Vocabulary + -- + -- | See the \"Reification Vocabulary\" section at <http://www.w3.org/TR/rdf-schema/#ch_reificationvocab>.+ , rdfStatement + , rdfSubject + , rdfPredicate + , rdfObject + -- ** Utility Properties + -- + -- | See the \"Utility Properties\" section at <http://www.w3.org/TR/rdf-schema/#ch_utilvocab>.+ , rdfsSeeAlso+ , rdfsIsDefinedBy+ , rdfValue + + )+where++import Swish.Utils.Namespace (Namespace, makeNamespace, ScopedName, makeNSScopedName)++import Data.Maybe (fromMaybe)+import Network.URI (URI, parseURI)++import qualified Data.Text as T++------------------------------------------------------------+-- Namespaces+------------------------------------------------------------++rdfURI, rdfsURI :: URI+rdfURI = fromMaybe (error "Internal error processing RDF URI") $ parseURI "http://www.w3.org/1999/02/22-rdf-syntax-ns#"+rdfsURI = fromMaybe (error "Internal error processing RDFS URI") $ parseURI "http://www.w3.org/2000/01/rdf-schema#"++-- | Maps @rdf@ to <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.+namespaceRDF :: Namespace+namespaceRDF = makeNamespace (Just "rdf") rdfURI++-- | Maps @rdfs@ to <http://www.w3.org/2000/01/rdf-schema#>.+namespaceRDFS :: Namespace+namespaceRDFS = makeNamespace (Just "rdfs") rdfsURI++------------------------------------------------------------+-- Terms+------------------------------------------------------------++toRDF, toRDFS :: T.Text -> ScopedName+toRDF = makeNSScopedName namespaceRDF+toRDFS = makeNSScopedName namespaceRDFS++-- | @rdf:RDF@.+rdfRDF :: ScopedName+rdfRDF = toRDF "RDF"++-- | @rdf:Description@.+rdfDescription :: ScopedName+rdfDescription = toRDF "Description"+ +-- | @rdf:datatype@.+rdfDatatype :: ScopedName+rdfDatatype = toRDF "datatype"++-- | @rdf:resource@.+rdfResource :: ScopedName+rdfResource = toRDF "resource"++-- | @rdf:about@.+rdfAbout :: ScopedName+rdfAbout = toRDF "about"++-- | @rdf:ID@.+rdfID :: ScopedName+rdfID = toRDF "ID"++-- | @rdf:parseType@.+rdfParseType :: ScopedName+rdfParseType = toRDF "parseType"++-- | @rdf:li@.+rdfLi :: ScopedName+rdfLi = toRDF "li"++-- | @rdf:nodeID@.+rdfNodeID :: ScopedName+rdfNodeID = toRDF "nodeID"++-- | Create a @rdf:_n@ entity.+rdfn :: Int -> ScopedName+rdfn = toRDF . T.pack . ("_" ++) . show++-- | @rdf:_1@.+rdf1 :: ScopedName+rdf1 = toRDF "_1"++-- | @rdf:_2@.+rdf2 :: ScopedName+rdf2 = toRDF "_2"++-- | @rdf:first@ from <http://www.w3.org/TR/rdf-schema/#ch_first>.+rdfFirst :: ScopedName+rdfFirst = toRDF "first"++-- | @rdf:rest@ from <http://www.w3.org/TR/rdf-schema/#ch_rest>.+rdfRest :: ScopedName+rdfRest = toRDF "rest"++-- | @rdf:nil@ from <http://www.w3.org/TR/rdf-schema/#ch_nil>.+rdfNil :: ScopedName+rdfNil = toRDF "nil"++-- | @rdf:type@ from <http://www.w3.org/TR/rdf-schema/#ch_type>.+rdfType :: ScopedName+rdfType = toRDF "type"++-- | @rdf:Property@ from <http://www.w3.org/TR/rdf-schema/#ch_property>.+rdfProperty :: ScopedName+rdfProperty = toRDF "Property"++-- | @rdf:XMLLiteral@ from <http://www.w3.org/TR/rdf-schema/#ch_xmlliteral>.+rdfXMLLiteral :: ScopedName+rdfXMLLiteral = toRDF "XMLLiteral"++-- | @rdfs:Resource@ from <http://www.w3.org/TR/rdf-schema/#ch_resource>.+rdfsResource :: ScopedName+rdfsResource = toRDFS "Resource"++-- | @rdfs:Class@ from <http://www.w3.org/TR/rdf-schema/#ch_class>.+rdfsClass :: ScopedName+rdfsClass = toRDFS "Class"++-- | @rdfs:Literal@ from <http://www.w3.org/TR/rdf-schema/#ch_literal>.+rdfsLiteral :: ScopedName+rdfsLiteral = toRDFS "Literal"++-- | @rdfs:Datatype@ from <http://www.w3.org/TR/rdf-schema/#ch_datatype>.+rdfsDatatype :: ScopedName+rdfsDatatype = toRDFS "Datatype"++-- | @rdfs:label@ from <http://www.w3.org/TR/rdf-schema/#ch_label>.+rdfsLabel :: ScopedName+rdfsLabel = toRDFS "label"++-- | @rdfs:comment@ from <http://www.w3.org/TR/rdf-schema/#ch_comment>.+rdfsComment :: ScopedName+rdfsComment = toRDFS "comment"++-- | @rdfs:range@ from <http://www.w3.org/TR/rdf-schema/#ch_range>.+rdfsRange :: ScopedName+rdfsRange = toRDFS "range"++-- | @rdfs:domain@ from <http://www.w3.org/TR/rdf-schema/#ch_domain>.+rdfsDomain :: ScopedName+rdfsDomain = toRDFS "domain"++-- | @rdfs:subClassOf@ from <http://www.w3.org/TR/rdf-schema/#ch_subclassof>.+rdfsSubClassOf :: ScopedName+rdfsSubClassOf = toRDFS "subClassOf"++-- | @rdfs:subPropertyOf@ from <http://www.w3.org/TR/rdf-schema/#ch_subpropertyof>.+rdfsSubPropertyOf :: ScopedName+rdfsSubPropertyOf = toRDFS "subPropertyOf"++-- | @rdfs:Container@ from <http://www.w3.org/TR/rdf-schema/#ch_container>.+rdfsContainer :: ScopedName+rdfsContainer = toRDFS "Container"++-- | @rdf:Bag@ from <http://www.w3.org/TR/rdf-schema/#ch_bag>.+rdfBag :: ScopedName+rdfBag = toRDF "Bag"++-- | @rdf:Seq@ from <http://www.w3.org/TR/rdf-schema/#ch_seq>.+rdfSeq :: ScopedName+rdfSeq = toRDF "Seq"++-- | @rdf:Alt@ from <http://www.w3.org/TR/rdf-schema/#ch_alt>.+rdfAlt :: ScopedName+rdfAlt = toRDF "Alt"++-- | @rdfs:ContainerMembershipProperty@ from <http://www.w3.org/TR/rdf-schema/#ch_containermembershipproperty>.+rdfsContainerMembershipProperty :: ScopedName+rdfsContainerMembershipProperty = toRDFS "ContainerMembershipProperty"++-- | @rdfs:member@ from <http://www.w3.org/TR/rdf-schema/#ch_member>.+rdfsMember :: ScopedName+rdfsMember = toRDFS "member"++-- | @rdf:List@ from <http://www.w3.org/TR/rdf-schema/#ch_list>.+rdfList :: ScopedName+rdfList = toRDF "List"++-- | @rdf:Statement@ from <http://www.w3.org/TR/rdf-schema/#ch_statement>.+rdfStatement :: ScopedName+rdfStatement = toRDF "Statement"++-- | @rdf:subject@ from <http://www.w3.org/TR/rdf-schema/#ch_subject>.+rdfSubject :: ScopedName+rdfSubject = toRDF "subject"++-- | @rdf:predicate@ from <http://www.w3.org/TR/rdf-schema/#ch_predicate>.+rdfPredicate :: ScopedName+rdfPredicate = toRDF "subject"++-- | @rdf:object@ from <http://www.w3.org/TR/rdf-schema/#ch_object>.+rdfObject :: ScopedName+rdfObject = toRDF "object"++-- | @rdfs:seeAlso@ from <http://www.w3.org/TR/rdf-schema/#ch_seealso>.+rdfsSeeAlso :: ScopedName+rdfsSeeAlso = toRDFS "seeAlso"++-- | @rdfs:isDefinedBy@ from <http://www.w3.org/TR/rdf-schema/#ch_isdefinedby>.+rdfsIsDefinedBy :: ScopedName+rdfsIsDefinedBy = toRDFS "isDefinedBy"++-- | @rdf:value@ from <http://www.w3.org/TR/rdf-schema/#ch_value>.+rdfValue :: ScopedName+rdfValue = toRDF "value"++--------------------------------------------------------------------------------+--+-- 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+--+--------------------------------------------------------------------------------
+ src/Swish/RDF/Vocabulary/XSD.hs view
@@ -0,0 +1,201 @@+{-# LANGUAGE OverloadedStrings #-}++--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : Swish.RDF.Vocabulary.XSD+-- Copyright : (c) 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : OverloadedStrings+--+-- This module defines vocabulary terms from the XSD document.+--+--------------------------------------------------------------------------------++module Swish.RDF.Vocabulary.XSD+ ( + namespaceXSD+ + -- * XSD data types+ -- + -- | See the XSD Schema Part 2 documentation at <http://www.w3.org/TR/xmlschema-2/>;+ -- the version used is \"W3C Recommendation 28 October 2004\", + -- <http://www.w3.org/TR/2004/REC-xmlschema-2-20041028/>. + , xsdType + + -- ** Primitive datatypes + -- + -- | See the section \"Primitive datatypes\" at + -- <http://www.w3.org/TR/xmlschema-2/#built-in-primitive-datatypes>. + , xsdString+ , xsdBoolean+ , xsdDecimal+ , xsdFloat+ , xsdDouble+ , xsdDateTime+ , xsdTime+ , xsdDate+ , xsdAnyURI + + -- ** Derived datatypes + -- + -- | See the section \"Derived datatypes\" at + -- <http://www.w3.org/TR/xmlschema-2/#built-in-derived>. + , xsdInteger+ , xsdNonPosInteger+ , xsdNegInteger+ , xsdLong+ , xsdInt+ , xsdShort+ , xsdByte+ , xsdNonNegInteger+ , xsdUnsignedLong+ , xsdUnsignedInt+ , xsdUnsignedShort+ , xsdUnsignedByte+ , xsdPosInteger++ )+where++import Swish.Utils.Namespace (Namespace, makeNamespace, ScopedName, makeNSScopedName)++import Data.Maybe (fromMaybe)+import Network.URI (URI, parseURI)++import qualified Data.Text as T++------------------------------------------------------------+-- Namespace+------------------------------------------------------------++xsdURI :: URI+xsdURI = fromMaybe (error "Internal error processing XSD URI") $ parseURI "http://www.w3.org/2001/XMLSchema#"++-- | Maps @xsd@ to <http://www.w3.org/2001/XMLSchema#>.+namespaceXSD :: Namespace+namespaceXSD = makeNamespace (Just "xsd") xsdURI++------------------------------------------------------------+-- Terms+------------------------------------------------------------++-- | Create a scoped name for an XSD datatype with the given name.+xsdType :: T.Text -> ScopedName+xsdType = makeNSScopedName namespaceXSD++-- | @xsd:string@ from <http://www.w3.org/TR/xmlschema-2/#string>.+xsdString :: ScopedName+xsdString = xsdType "string"++-- | @xsd:boolean@ from <http://www.w3.org/TR/xmlschema-2/#boolean>.+xsdBoolean :: ScopedName+xsdBoolean = xsdType "boolean"++-- | @xsd:decimal@ from <http://www.w3.org/TR/xmlschema-2/#decimal>.+xsdDecimal :: ScopedName+xsdDecimal = xsdType "decimal"++-- | @xsd:integer@ from <http://www.w3.org/TR/xmlschema-2/#integer>.+xsdInteger :: ScopedName+xsdInteger = xsdType "integer"++-- | @xsd:nonNegativeInteger@ from <http://www.w3.org/TR/xmlschema-2/#nonNegativeInteger>.+xsdNonNegInteger :: ScopedName+xsdNonNegInteger = xsdType "nonNegativeInteger"++-- | @xsd:nonPositiveInteger@ from <http://www.w3.org/TR/xmlschema-2/#nonPositiveInteger>.+xsdNonPosInteger :: ScopedName+xsdNonPosInteger = xsdType "nonPositiveInteger"++-- | @xsd:positiveInteger@ from <http://www.w3.org/TR/xmlschema-2/#positiveInteger>.+xsdPosInteger :: ScopedName+xsdPosInteger = xsdType "positiveInteger"++-- | @xsd:negativeInteger@ from <http://www.w3.org/TR/xmlschema-2/#negativeInteger>.+xsdNegInteger :: ScopedName+xsdNegInteger = xsdType "negativeInteger"++-- | @xsd:float@ from <http://www.w3.org/TR/xmlschema-2/#float>.+xsdFloat :: ScopedName+xsdFloat = xsdType "float"++-- | @xsd:double@ from <http://www.w3.org/TR/xmlschema-2/#double>.+xsdDouble :: ScopedName+xsdDouble = xsdType "double"++-- | @xsd:long@ from <http://www.w3.org/TR/xmlschema-2/#long>.+xsdLong :: ScopedName+xsdLong = xsdType "long"++-- | @xsd:int@ from <http://www.w3.org/TR/xmlschema-2/#int>.+xsdInt :: ScopedName+xsdInt = xsdType "int"++-- | @xsd:short@ from <http://www.w3.org/TR/xmlschema-2/#short>.+xsdShort :: ScopedName+xsdShort = xsdType "short"++-- | @xsd:byte@ from <http://www.w3.org/TR/xmlschema-2/#byte>.+xsdByte :: ScopedName+xsdByte = xsdType "byte"++-- | @xsd:unsignedLong@ from <http://www.w3.org/TR/xmlschema-2/#unsignedLong>.+xsdUnsignedLong :: ScopedName+xsdUnsignedLong = xsdType "unsignedLong"++-- | @xsd:unsignedInt@ from <http://www.w3.org/TR/xmlschema-2/#unsignedInt>.+xsdUnsignedInt :: ScopedName+xsdUnsignedInt = xsdType "unsignedInt"++-- | @xsd:unsignedShort@ from <http://www.w3.org/TR/xmlschema-2/#unsignedShort>.+xsdUnsignedShort :: ScopedName+xsdUnsignedShort = xsdType "unsignedShort"++-- | @xsd:unsignedByte@ from <http://www.w3.org/TR/xmlschema-2/#unsignedByte>.+xsdUnsignedByte :: ScopedName+xsdUnsignedByte = xsdType "unsignedByte"++-- | @xsd:date@ from <http://www.w3.org/TR/xmlschema-2/#date>.+xsdDate :: ScopedName+xsdDate = xsdType "date"++-- | @xsd:dateTime@ from <http://www.w3.org/TR/xmlschema-2/#dateTime>.+xsdDateTime :: ScopedName+xsdDateTime = xsdType "dateTime"++-- | @xsd:time@ from <http://www.w3.org/TR/xmlschema-2/#time>.+xsdTime :: ScopedName+xsdTime = xsdType "time"++-- | @xsd:anyURI@ from <http://www.w3.org/TR/xmlschema-2/#anyURI>.+xsdAnyURI :: ScopedName+xsdAnyURI = xsdType "anyURI"++--------------------------------------------------------------------------------+--+-- 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+--+--------------------------------------------------------------------------------
+ src/Swish/Utils/ListHelpers.hs view
@@ -0,0 +1,359 @@+--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : ListHelpers+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : H98+--+-- This module defines some generic list and related helper functions. +--+--------------------------------------------------------------------------------++{-+TODO:++The plan is to use modules such as Data.Set where appropriate.++-}++module Swish.Utils.ListHelpers+ ( -- list of Swish.RDF.xxx modules the routine is used in+ select -- GraphMatch+ , deleteIndex -- Datatype+ , subset -- Proof, RDFProof, VarBinding [also defined in Utils.PartOrderedCollection]+ , equiv -- GraphMatch, RDFRuleset, SwishScript, VarBinding, Utils.LookupMap+ , addSetElem -- RDFGraph+ , headOrNothing -- VarBinding+ , pairUngroup -- GraphMatch+ , pairSort -- GraphMatch+ , pairGroup -- GraphMatch+ , breakAll -- SwishMain+ , powerSet -- ClassRestrictionRule, RDFProof+ , permutations -- VarBinding+ , listProduct -- RDFQuery+ , powerSequencesLen -- RDFProof+ , flist -- Datatype, RDFProof, RDFRuleset, SwishScript, VarBinding+ , allp -- RDFQuery+ , anyp -- RDFQuery+ + )+where+ +import Data.Ord (comparing) +import Data.List (sortBy, groupBy)++------------------------------------------------------------+-- Generic helpers+------------------------------------------------------------++-- |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"++-- |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 ]+-}++------------------------------------------------------------+-- Set functions+--+-- NOTE: to change to Data.Set then Eq a constraint will +-- likely need changing to Ord a+------------------------------------------------------------++-- |Subset test++subset :: (Eq a) => [a] -> [a] -> Bool+a `subset` b = and [ ma `elem` b | ma <- a ]++-- |Set equivalence test++equiv :: (Eq a) => [a] -> [a] -> Bool+a `equiv` b = a `subset` b && b `subset` a++-- |Add element to set++addSetElem :: (Eq a) => a -> [a] -> [a]+addSetElem e es = if e `elem` es then es else e:es++------------------------------------------------------------+-- Lists and Maybes+------------------------------------------------------------++-- |Return head of a list of @Maybe@'s, or @Nothing@ if list is empty+--+-- Use with @filter isJust@ to select a non-Nothing value from a+-- list when such a value is present.+--+headOrNothing :: [Maybe a] -> Maybe a+headOrNothing [] = Nothing+headOrNothing (a:_) = a++------------------------------------------------------------+-- 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)+-}++pairUngroup :: (a,[b]) -> [(a,b)]+pairUngroup (a,bs) = [ (a,b) | b <- bs ]++pairSort :: (Ord a) => [(a,b)] -> [(a,b)]+pairSort = sortBy (comparing fst)++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 a b = fst a == fst b++------------------------------------------------------------+-- Separate list into sublists+------------------------------------------------------------++-- |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')++------------------------------------------------------------+-- Powerset+------------------------------------------------------------++-- [[[TBD... there's a much better implementation in my email,+-- from Christopher Hendrie. This is the raw code.]]]+{-+>ranked_powerset :: [a] -> [[[a]]]+>ranked_powerset = takeWhile (not . null) . foldr next_powerset ([[]] :+repeat [])+>+>next_powerset :: a -> [[[a]]] -> [[[a]]]+>next_powerset x r = zipWith (++) ([] : map (map (x:)) r) r+>+>powerset :: [a] -> [[a]]+>powerset = tail . concat . ranked_powerset+-}++-- |Powerset of a list, in ascending order of size.+-- Assumes the supplied list has no duplicate elements.+powerSet :: [a] -> [[a]]+powerSet as =+ concatMap (`combinations` as) [1..length as]++-- |Combinations of n elements from a list, each being returned in the+-- order that they appear in the list.+combinations :: Int -> [a] -> [[a]]+combinations _ [] = [] -- Don't include empty combinations+combinations n as@(ah:at)+ | n <= 0 = [[]]+ | n > length as = []+ | n == length as = [as]+ | otherwise = map (ah:) (combinations (n-1) at) +++ combinations n at++{-+-- |Return list of integers from lo to hi.+intRange :: Int -> Int -> [Int]+intRange lo hi = take (hi-lo+1) (iterate (+1) 1)+-}++{-+-- Tests+testcomb0 = combinations 0 "abcd" -- []+testcomb1 = combinations 1 "abcd" -- ["a","b","c","d"]+testcomb2 = combinations 2 "abcd" -- ["ab","ac","ad","bc","bd","cd"]+testcomb3 = combinations 3 "abcd" -- ["abc","abd","acd","bcd"]+testcomb4 = combinations 4 "abcd" -- ["abcd"]+testcomb5 = combinations 5 "abcd" -- []+testpower = powerSet "abc" -- ["a","b","c","ab","ac","bc","abc"]+-}++------------------------------------------------------------+-- Permutations of a list+------------------------------------------------------------++-- This algorithm is copied from an email by S.D.Mechveliani+-- http://www.dcs.gla.ac.uk/mail-www/haskell/msg01936.html+permutations :: [a] -> [[a]]+permutations [] = [[]]+permutations (j:js) = addOne $ permutations js+ where+ addOne = foldr ((++) . ao) []++ ao [] = [[j]]+ ao (k:ks) = (j:k:ks) : map (k:) (ao ks)++{-+testperm = permutations [1,2,3] ==+ [[1,2,3],[2,1,3],[2,3,1],[1,3,2],[3,1,2],[3,2,1]]+-}++------------------------------------------------------------+-- List product+------------------------------------------------------------++-- |Given a list of lists, construct a new list of lists where+-- each member of the new list is the same length as the original+-- list, and each member corresponds to a different choice of+-- one element from each of the original members in the+-- corresponding position. Thus:+--+-- > listProduct [[a1,a2],[b1],[c1,c2]] =+-- > [ [a1,b1,c1], [a1,b1,c2], [a2,b1,c1], [a2,b1,c2] ]+--+-- Note: The length of the resulting list is the prodicty of+-- lengths of the components of the original list. Thus, if+-- any member of the original list is empty then so is the+-- resulting list:+--+-- > listProduct [[a1,a2],[],[c1,c2]] = []+--+-- NOTE: this is subsumed by 'sequence'+--+listProduct :: [[a]] -> [[a]]+listProduct [] = [[]]+listProduct (as:ass) = concat [ map (a:) (listProduct ass) | a <- as ]++{-+test1 = listProduct [["a1","a2"],["b1"],["c1","c2"]]+test2 = listProduct [["a1","a2"],[],["c1","c2"]]++lp [] = [[]]+lp (as:ass) = concatMap (\a -> (map (a:) (lp ass))) as+-}++------------------------------------------------------------+-- 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++------------------------------------------------------------+-- Functions, lists and monads+------------------------------------------------------------++-- |Apply list of functions to some value, returning list of results.+-- It's kind of like an converse map.+--+-- This is similar to the 'ap' function in the Monad library.+--+flist :: [a->b] -> a -> [b]+flist fs a = map ($ a) fs++{-+flisttest = flist [(1*),(2*),(3*)] 5 -- [5,10,15]+-}++{-++-- |A more generalized form of flist that works with arbitrary Monads.+-- (Suggested by Derek Elkin.)++fmonad :: Monad m => m (a->b) -> a -> m b+fmonad fm a =+ do { f <- fm+ ; return $ f a+ }++-}++{-+fmonadtest = fmonad [(1*),(2*),(3*)] 3 -- [3,6,9]+-}++-- |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]+-}+++--------------------------------------------------------------------------------+--+-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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+--+--------------------------------------------------------------------------------
+ src/Swish/Utils/LookupMap.hs view
@@ -0,0 +1,454 @@+{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}+{-# LANGUAGE FlexibleContexts, FlexibleInstances, TypeSynonymInstances #-}+{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable #-}+--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : LookupMap+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : A lot of LANGUAGE extensions...+--+-- This module defines a lookup table format and associated functions+-- used by the graph matching code.+--+--------------------------------------------------------------------------------++------------------------------------------------------------+-- Generic list-of-pairs lookup functions+------------------------------------------------------------++module Swish.Utils.LookupMap+ ( LookupEntryClass(..), LookupMap(..)+ , emptyLookupMap, makeLookupMap, listLookupMap+ , reverseLookupMap+ , keyOrder+ , mapFind, mapFindMaybe, mapContains+ , mapReplace, mapReplaceOrAdd, mapReplaceAll, mapReplaceMap+ , mapAdd, mapAddIfNew+ , mapDelete, mapDeleteAll+ , mapApplyToAll, mapTranslate+ , mapEq, mapKeys, mapVals+ , mapSelect, mapMerge+ , mapTranslateKeys, mapTranslateVals+ , mapTranslateEntries, mapTranslateEntriesM++ )+ where++import qualified Data.Foldable as F+import qualified Data.Traversable as T+import qualified Data.List as L++import Control.Arrow (first, second)++import Data.Ord (comparing)++import Swish.Utils.ListHelpers (equiv)++-- this is in Data.Tuple in base 4.3+swap :: (a,b) -> (b,a)+swap (a,b) = (b,a)++------------------------------------------------------------+-- Class for lookup map entries+------------------------------------------------------------++-- |@LookupEntryClass@ defines essential functions of any datatype+-- that can be used to make a 'LookupMap'.+--+-- Minimal definition: @newEntry@ and @keyVal@+--+class (Eq k, Show k) => LookupEntryClass a k v | a -> k, a -> v+ where+ newEntry :: (k,v) -> a+ keyVal :: a -> (k,v)+ + entryKey :: a -> k+ entryKey = fst . keyVal+ + entryVal :: a -> v+ entryVal = snd . keyVal+ + entryEq :: (Eq v) => a -> a -> Bool+ entryEq e1 e2 = keyVal e1 == keyVal e2+ + entryShow :: (Show v) => a -> String+ entryShow e = show k ++ ":" ++ show v where (k,v) = keyVal e+ + kmap :: (LookupEntryClass a2 k2 v) => (k -> k2) -> a -> a2+ kmap f = newEntry . first f . keyVal+ + vmap :: (LookupEntryClass a2 k v2) => (v -> v2) -> a -> a2+ vmap f = newEntry . second f . keyVal++-- |Predefine a pair of appropriate values as a valid lookup table entry+-- (i.e. an instance of LookupEntryClass).+--+instance (Eq k, Show k) => LookupEntryClass (k,v) k v where+ newEntry = id+ keyVal = id++-- |Define a lookup map based on a list of values.+--+-- Note: the class constraint that a is an instance of 'LookupEntryClass'+-- is not defined here, for good reasons (which I forget right now, but+-- something to do with the method dictionary being superfluous on+-- an algebraic data type).+--+data LookupMap a = LookupMap [a]+ deriving (Functor, F.Foldable, T.Traversable)++{- +TODO: could add++instance Monoid (LookupMap a) where+ mempty = LookupMap []+ mappend = mapMerge++but may need constraints on a, do not+want to add instances at this time, and is+it really useful? ++-}++gLM :: LookupMap a -> [a]+gLM (LookupMap es) = es++-- TODO: See also 'mapEq'+-- (why not just use that for the Eq instance? I don't know: it's probably historic.)+--++-- |Define equality of 'LookupMap' values based on equality of entries.+--+-- (This is possibly a poor definition, as it is dependent on ordering+-- of list members. But it passes all current test cases, and is used+-- only for testing.)+--+instance (Eq a) => Eq (LookupMap a) where+ LookupMap es1 == LookupMap es2 = es1 == es2++-- |Define Show instance for LookupMap based on Showing the+-- list of entries.+--+instance (Show a ) => Show (LookupMap a) where+ show (LookupMap es) = "LookupMap " ++ show es++{-+TODO: should the LookupEntryClass constraint be removed from+emptyLookupMap and makeLookupMap?++I guess not since LookupMap is exported, so users can use+that if they do not need the constraint.+-}++-- |Empty lookup map of arbitrary (i.e. polymorphic) type.+--+emptyLookupMap :: (LookupEntryClass a k v) => LookupMap a+emptyLookupMap = LookupMap []++-- |Function to create a `LookupMap` from a list of entries.+--+-- Currently, this is trivial but future versions could be+-- more substantial.+--+makeLookupMap :: (LookupEntryClass a k v) => [a] -> LookupMap a+makeLookupMap = LookupMap++-- |Return list of lookup map entries.+--+-- Currently, this is trivial but future versions could be+-- more substantial.+--+listLookupMap :: (LookupEntryClass a k v) => LookupMap a -> [a]+listLookupMap = gLM++-- |Given a lookup map entry, return a new entry that can be used+-- in the reverse direction of lookup. This is used to construct+-- a reverse LookupMap.+--+reverseEntry :: (LookupEntryClass a1 k v, LookupEntryClass a2 v k)+ => a1 -> a2+reverseEntry = newEntry . swap . keyVal++-- |Given a lookup map, return a new map that can be used+-- in the opposite direction of lookup.+--+reverseLookupMap :: (LookupEntryClass a1 b c, LookupEntryClass a2 c b)+ => LookupMap a1 -> LookupMap a2+reverseLookupMap = fmap reverseEntry++-- |Given a pair of lookup entry values, return the ordering of their+-- key values.+--+keyOrder :: (LookupEntryClass a k v, Ord k)+ => a -> a -> Ordering+keyOrder = comparing entryKey++-- Local helper function to build a new LookupMap from+-- a new entry and an exiting map.+--+mapCons :: a -> LookupMap a -> LookupMap a+mapCons e (LookupMap es) = LookupMap (e:es)++-- |Find key in lookup map and return corresponding value,+-- otherwise return default supplied.+--+mapFind :: (LookupEntryClass a k v) => v -> k -> LookupMap a -> v+mapFind def key (LookupMap es) = foldr match def es where+ match ent alt+ | key == entryKey ent = entryVal ent+ | otherwise = alt++-- |Find key in lookup map and return Just the corresponding value,+-- otherwise return Nothing.+--+mapFindMaybe :: (LookupEntryClass a k v) => k -> LookupMap a -> Maybe v+mapFindMaybe key (LookupMap es) = foldr match Nothing es where+ match ent alt+ | key == entryKey ent = Just (entryVal ent)+ | otherwise = alt++-- |Test to see if key is present in the supplied map+--+mapContains :: (LookupEntryClass a k v) =>+ LookupMap a -> k -> Bool+mapContains (LookupMap es) key = any match es where+ match ent = key == entryKey ent++-- |Replace an existing occurrence of a key a with a new key-value pair.+-- +-- The resulting lookup map has the same form as the original in all+-- other respects. Assumes exactly one occurrence of the supplied key.+--+mapReplace :: (LookupEntryClass a k v) =>+ LookupMap a -> a -> LookupMap a+mapReplace (LookupMap (e:es)) newe+ | entryKey e == entryKey newe = LookupMap (newe:es)+ | otherwise = mapAdd more e where+ more = mapReplace (LookupMap es) newe+mapReplace _ newe =+ error ("mapReplace: Key value not found in lookup table: "+++ Prelude.show (entryKey newe))++-- |Replace an existing occurrence of a key a with a new key-value pair,+-- or add a new key-value pair if the supplied key is not already present.+--+mapReplaceOrAdd :: (LookupEntryClass a k v) =>+ a -> LookupMap a -> LookupMap a+mapReplaceOrAdd newe (LookupMap (e:es))+ | entryKey e == entryKey newe = LookupMap (newe:es)+ | otherwise = mapCons e more where+ more = mapReplaceOrAdd newe (LookupMap es)+mapReplaceOrAdd newe (LookupMap []) = LookupMap [newe]++-- |Replace any occurrence of a key a with a new key-value pair.+--+-- The resulting lookup map has the same form as the original in all+-- other respects.+--+mapReplaceAll :: (LookupEntryClass a k v) =>+ LookupMap a -> a -> LookupMap a+mapReplaceAll (LookupMap (e:es)) newe = mapCons e' more where+ more = mapReplaceAll (LookupMap es) newe+ e' = if entryKey e == entryKey newe then newe else e+mapReplaceAll (LookupMap []) _ = LookupMap []++-- |Replace any occurrence of a key in the first argument with a+-- corresponding key-value pair from the second argument, if present.+--+-- This could be implemented by multiple applications of 'mapReplaceAll',+-- but is arranged differently so that only one new @LookupMap@ value is+-- created.+--+-- Note: keys in the new map that are not present in the old map+-- are not included in the result map+--+mapReplaceMap :: (LookupEntryClass a k v) =>+ LookupMap a -> LookupMap a -> LookupMap a+mapReplaceMap (LookupMap (e:es)) newmap = mapCons e' more where+ more = mapReplaceMap (LookupMap es) newmap+ e' = newEntry (k, mapFind v k newmap)+ (k,v) = keyVal e+mapReplaceMap (LookupMap []) _ = LookupMap []++-- |Add supplied key-value pair to the lookup map.+--+-- This is effectively an optimized case of 'mapReplaceOrAdd' or 'mapAddIfNew',+-- where the caller guarantees to avoid duplicate key values.+--+mapAdd :: LookupMap a -> a -> LookupMap a+mapAdd emap e = mapCons e emap++-- |Add supplied key-value pair to the lookup map,+-- only if the key value is not already present.+--+mapAddIfNew :: (LookupEntryClass a k v) =>+ LookupMap a -> a -> LookupMap a+mapAddIfNew emap e = if mapContains emap (entryKey e)+ then emap+ else mapCons e emap++-- |Delete supplied key value from the lookup map.+--+-- This function assumes exactly one occurrence.+--+mapDelete :: (LookupEntryClass a k v) =>+ LookupMap a -> k -> LookupMap a+mapDelete (LookupMap (e:es)) k+ | k == entryKey e = LookupMap es+ | otherwise = mapCons e more where+ more = mapDelete (LookupMap es) k+mapDelete _ k =+ error ("mapDelete: Key value not found in lookup table: " ++ Prelude.show k)++-- |Delete any occurrence of a supplied key value from the lookup map.+--+mapDeleteAll :: (LookupEntryClass a k v) =>+ LookupMap a -> k -> LookupMap a+mapDeleteAll (LookupMap (e:es)) k =+ if entryKey e == k then more else mapCons e more where+ more = mapDeleteAll (LookupMap es) k+mapDeleteAll (LookupMap []) _ = LookupMap []++-- |Return a list of values obtained by applying a function to each key+-- in the map. Creates an alternative set of values that can be+-- retrieved using mapTranslate.+--+mapApplyToAll :: (LookupEntryClass a k v) =>+ LookupMap a -> (k -> w) -> [w]+mapApplyToAll es f = gLM $ fmap (f . entryKey) es++-- |Find a node in a lookup map list, and returns the+-- corresponding value from a supplied list. The appropriate ordering+-- of the list is not specified here, but an appropriately ordered list+-- may be obtained by 'mapApplyToAll'.+--+mapTranslate :: (LookupEntryClass a k v) =>+ LookupMap a -> [w] -> k -> w -> w+mapTranslate (LookupMap (e:es)) (w:ws) k def+ | k == entryKey e = w+ | otherwise = mapTranslate (LookupMap es) ws k def+mapTranslate _ _ _ def = def++-- |Compare two lookup maps for equality.+--+-- Two maps are equal if they have the same set of keys, and if+-- each key maps to an equivalent value.+--+mapEq :: (LookupEntryClass a k v, Eq v) =>+ LookupMap a -> LookupMap a -> Bool+mapEq es1 es2 =+ ks1 `equiv` ks2 &&+ and [ mapFindMaybe k es1 == mapFindMaybe k es2 | k <- ks1 ]+ where+ ks1 = mapKeys es1+ ks2 = mapKeys es2++-- |Return the list of keys in a supplied LookupMap+--+mapKeys :: (LookupEntryClass a k v) =>+ LookupMap a -> [k]+mapKeys (LookupMap es) = L.nub $ map entryKey es++-- |Return list of distinct values in a supplied LookupMap+--+mapVals :: (Eq v, LookupEntryClass a k v) =>+ LookupMap a -> [v]+mapVals (LookupMap es) = L.nub $ map entryVal es++-- |Select portion of a lookup map that corresponds to+-- a supplied list of keys+--+mapSelect :: (LookupEntryClass a k v) =>+ LookupMap a -> [k] -> LookupMap a+mapSelect (LookupMap es) ks =+ LookupMap $ filter (keyIn ks) es+ where+ keyIn iks e = entryKey e `elem` iks++-- |Merge two lookup maps, ensuring that if the same key appears+-- in both maps it is associated with the same value.+--+mapMerge :: (LookupEntryClass a k v, Eq a, Show a, Ord k) =>+ LookupMap a -> LookupMap a -> LookupMap a+mapMerge (LookupMap s1) (LookupMap s2) =+ LookupMap $ merge (L.sortBy keyOrder s1) (L.sortBy keyOrder s2)+ where+ merge es1 [] = es1+ merge [] es2 = es2+ merge es1@(e1:et1) es2@(e2:et2) =+ case keyOrder e1 e2 of+ LT -> e1 : merge et1 es2+ GT -> e2 : merge es1 et2+ EQ -> if e1 /= e2+ then error ("mapMerge key conflict: " ++ show e1+ ++ " with " ++ show e2)+ else e1 : merge et1 et2++-- |An fmap-like function that returns a new lookup map that is a+-- copy of the supplied map with entry keys replaced according to+-- a supplied function.+--+mapTranslateKeys :: (LookupEntryClass a1 k1 v, LookupEntryClass a2 k2 v) =>+ (k1 -> k2) -> LookupMap a1 -> LookupMap a2+mapTranslateKeys f = fmap (kmap f)++-- |An fmap-like function that returns a new lookup map that is a+-- copy of the supplied map with entry values replaced according to+-- a supplied function.+--+mapTranslateVals :: (LookupEntryClass a1 k v1, LookupEntryClass a2 k v2) =>+ (v1 -> v2) -> LookupMap a1 -> LookupMap a2+mapTranslateVals f = fmap (vmap f)++-- |A function that returns a new lookup map that is a copy of the+-- supplied map with complete entries replaced according to+-- a supplied function.+--+mapTranslateEntries :: (a1 -> a2) -> LookupMap a1 -> LookupMap a2+mapTranslateEntries = fmap++-- |A monadic form of `mapTranslateEntries` which is+-- the same as `Data.Traversable.mapM`.+--+-- Since `LookupMap` now has a `Data.Traversable.Traversable` instance+-- this is just `T.mapM`.+--+mapTranslateEntriesM :: (Monad m)+ => (a1 -> m a2) -> LookupMap a1 -> m (LookupMap a2)+mapTranslateEntriesM = T.mapM +{-+mapTranslateEntriesM f (LookupMap es) =+ do { m2 <- mapM f es+ ; return $ LookupMap m2+ }+-}++--------------------------------------------------------------------------------+--+-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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+--+--------------------------------------------------------------------------------
+ src/Swish/Utils/MiscHelpers.hs view
@@ -0,0 +1,69 @@+--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : MiscHelpers+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : H98+--+-- This module defines a random set of helper functions+-- used by the graph handling code.+--+-- This module is *deprecated* and will be removed at the next possible+-- release.+--+--------------------------------------------------------------------------------++module Swish.Utils.MiscHelpers+ ( hash+ , hashModulus+ )+where++------------------------------------------------------------+-- Hash function and values+------------------------------------------------------------+--+-- Simple hash function based on Sedgewick, Algorithms in C, p 233+-- (choose mx*cm+255 < maxBound)+-- 'seed' is an additional parameter that allows the function+-- to be varied for re-hashing.++hashModulus :: Int+hashModulus = 16000001++hash :: Int -> String -> Int+hash seed = hash1 seed (64+seed) hashModulus ++hash1 :: Int -> Int -> Int -> String -> Int+hash1 sofar cm mx (c:str) = hash1 (( sofar*cm + fromEnum c ) `rem` mx) cm mx str+hash1 sofar _ _ [] = sofar+++--------------------------------------------------------------------------------+--+-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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+--+--------------------------------------------------------------------------------
+ src/Swish/Utils/Namespace.hs view
@@ -0,0 +1,222 @@+{-# LANGUAGE TypeSynonymInstances, MultiParamTypeClasses, FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}++--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : Namespace+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : TypeSynonymInstances, MultiParamTypeClasses, FlexibleInstances, 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.Utils.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.Utils.QName (QName, newQName, getQNameURI, getNamespace, getLocalName)+import Swish.Utils.LookupMap (LookupEntryClass(..))++import Data.Monoid (Monoid(..))+import Data.String (IsString(..))+import Data.Maybe (fromMaybe)++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+ +{- + {+ nsPrefix :: Maybe T.Text+ , nsURI :: URI+ }+-}+ +getNamespacePrefix :: Namespace -> Maybe T.Text+getNamespacePrefix (Namespace p _) = p++getNamespaceURI :: Namespace -> URI+getNamespaceURI (Namespace _ u) = u++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 Show Namespace where+ show (Namespace (Just p) u) = show p ++ ":<" ++ show u ++ ">"+ show (Namespace _ u) = "<" ++ show u ++ ">"++instance LookupEntryClass Namespace (Maybe T.Text) URI where+ keyVal (Namespace pre uri) = (pre,uri)+ newEntry (pre,uri) = Namespace pre uri++makeNamespace :: Maybe T.Text -> URI -> Namespace+makeNamespace = Namespace++makeNamespaceQName :: Namespace -> T.Text -> 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 T.Text++-- | Returns the local part.+getScopeLocal :: ScopedName -> T.Text+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 QNames are equal+instance Eq ScopedName where+ (ScopedName qn1 _ _) == (ScopedName qn2 _ _) = qn1 == qn2++-- | Scoped names are ordered by their QNames+instance Ord ScopedName where+ (ScopedName qn1 _ _) <= (ScopedName qn2 _ _) = qn1 <= qn2++-- | If there is a namespace associated then the Show instance+-- uses @prefix:local@, otherwise @<url>@.+instance Show ScopedName where+ show (ScopedName qn n l) = case getNamespacePrefix n of+ Just pre -> T.unpack $ mconcat [pre, ":", 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++-- for the moment leave matchName using String rather than Text++-- |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 from prefix, URI and local name+makeScopedName :: Maybe T.Text -> URI -> T.Text -> ScopedName+makeScopedName pre nsuri local =+ ScopedName (newQName nsuri local) (Namespace pre nsuri) local++-- |Construct a ScopedName from a QName.+makeQNameScopedName :: Maybe T.Text -> QName -> ScopedName+makeQNameScopedName pre qn = ScopedName qn (Namespace pre (getNamespace qn)) (getLocalName qn)++-- | Construct a ScopedName for a bare URI (the label is set to \"\").+makeURIScopedName :: URI -> ScopedName+makeURIScopedName uri = makeScopedName Nothing uri ""++-- | Construct a ScopedName from a Namespace and local component+makeNSScopedName :: Namespace -> T.Text -> 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 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+--+--------------------------------------------------------------------------------
+ src/Swish/Utils/PartOrderedCollection.hs view
@@ -0,0 +1,386 @@+--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : PartOrderedCollection+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 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 Swish.Utils.PartOrderedCollection+ ( PartCompare+ , minima+ , maxima+ , partCompareEq+ , partComparePair+ , partCompareListMaybe+ , partCompareListSubset+ , partCompareListPartOrd+ , partCompareMaybe+ + -- the following are currently unused+ , partCompareOrd+ , partCompareListOrd + )+ where++import Data.List (foldl')++------------------------------------------------------------+-- Type of partial compare 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 as = foldl' add [] as+ 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) => a -> a -> Maybe Ordering+partCompareEq a1 a2 = if a1 == a2 then Just EQ else Nothing++-- |Partial ordering for Ord values+partCompareOrd :: (Ord a) => a -> a -> Maybe Ordering+partCompareOrd a1 a2 = Just $ compare a1 a2++-- |Part-ordering comparison on pairs of values,+-- where each has a part-ordering relationship+partComparePair ::+ (a->a->Maybe Ordering) -> (b->b->Maybe Ordering) -> (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 on lists of Ord 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+--+partCompareListOrd :: (Ord a) => [a] -> [a] -> Maybe Ordering+partCompareListOrd = partCompareListPartOrd (Just `c2` compare)+ where c2 = (.) . (.)++-- |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 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+--+--------------------------------------------------------------------------------
+ src/Swish/Utils/QName.hs view
@@ -0,0 +1,277 @@+{-# LANGUAGE OverloadedStrings #-}++--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : QName+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : OverloadedStrings+--+-- This module defines an algebraic datatype for qualified names (QNames).+--+--------------------------------------------------------------------------------++-- 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.Utils.QName+ ( QName+ , newQName+ , qnameFromURI+ , getNamespace+ , getLocalName+ , getQNameURI+ , qnameFromFilePath+ )+ where++import System.Directory (canonicalizePath)++import Network.URI (URI(..), URIAuth(..)+ , parseURIReference)++import Data.String (IsString(..))+import Data.Maybe (fromMaybe)++import Data.Interned (intern, unintern)+import Data.Interned.URI (InternedURI)++import qualified Data.Text as T++------------------------------------------------------------+-- Qualified name+------------------------------------------------------------+--+-- cf. http://www.w3.org/TR/REC-xml-names/++{-| ++A qualified name, consisting of a namespace URI+and the local part of the identifier.++-}++{-+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 T.Text++-- | This is not total since it will fail if the input string is not a valid URI.+instance IsString QName where+ fromString = qnameFromString+ +-- | Equality is determined by a case sensitive comparison of the +-- URI.+instance Eq QName where+ -- see qnEq comments below+ (QName u1 _ _) == (QName u2 _ _) = u1 == u2++-- ugly, use show instance OR switch to the ordering of InternedURI+ +instance Ord QName where+ {-+ (QName u1 l1) <= (QName u2 l2) =+ if up1 /= up2 then up1 <= up2 else (ur1++l1) <= (ur2++l2)+ where+ n = min (length u1) (length u2)+ (up1,ur1) = splitAt n u1+ (up2,ur2) = splitAt n u2+ -}+ + -- TODO: which is faster?+ -- Now we have changed to InternedURI, we could use the+ -- Ord instance of it, but it is unclear to me what the+ -- ordering means in that case, and whether the semantics+ -- matter here?+ (QName u1 _ _) <= (QName u2 _ _) = show u1 <= show u2+ + {-+ (QName _ uri1 l1) <= (QName _ uri2 l2) =+ if up1 /= up2 then up1 <= up2 else (ur1 ++ T.unpack l1) <= (ur2 ++ T.unpack l2)+ where+ u1 = show uri1+ u2 = show uri2+ + n = min (length u1) (length u2)+ (up1,ur1) = splitAt n u1+ (up2,ur2) = splitAt n u2+ -}+ +-- | The format used to display the URI is @<uri>@.+instance Show QName where+ show (QName u _ _) = "<" ++ show u ++ ">"++{-+Should this be clever and ensure that local doesn't+contain /, say?++We could also me more clever, and safer, when constructing+the overall uri.+-}+newQName :: URI -> T.Text -> QName+newQName ns local = + let l = T.unpack local+ uristr = show ns ++ l+ uri = fromMaybe (error ("Unable to parse URI from: '" ++ show ns ++ "' + '" ++ l ++ "'")) (parseURIReference uristr)+ + {- the following does not work since the semantics of relativeTo do not match the required+ behavior here. It may well be better to do something like the following, writing a replacement for+ relativeTo, but leave that for a later date.+ + let l = T.unpack local+ luri = fromMaybe (error ("Unable to parse local name as a URI reference: '" ++ l ++ "'")) (parseRelativeReference l)+ uri = fromMaybe (error ("Unable to combine " ++ show ns ++ " with " ++ l)) $ luri `relativeTo` ns+ -}+ + in QName (intern uri) ns local++{-++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.++-}++qnameFromURI :: URI -> QName+qnameFromURI uri =+ let uf = uriFragment uri+ up = uriPath uri+ q0 = QName iuri uri ""+ iuri = intern uri+ in case uf of+ "#" -> q0+ '#':xs -> QName iuri (uri { uriFragment = "#" }) (T.pack xs)+ "" -> case break (=='/') (reverse up) of+ ("",_) -> q0 -- path ends in / or is empty+ (_,"") -> q0 -- path contains no /+ (rlname,rpath) -> QName iuri (uri {uriPath = reverse rpath}) (T.pack (reverse rlname))+ + e -> error $ "Unexpected: uri=" ++ show uri ++ " has fragment='" ++ show e ++ "'" ++qnameFromString :: String -> QName+qnameFromString s = + maybe (error ("Unable to convert '" ++ s ++ "' into a QName"))+ qnameFromURI (parseURIReference s)++-- | 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 -> T.Text+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++{-+Original used comparison of concatenated strings,+but that was very inefficient. The longer version below+does the comparison without constructing new values but is+no longer valid with the namespace being stored as a URI,+so for now just compare the overall URIs and we can+optimize this at a later date if needed.+qnEq :: QName -> QName -> Bool+qnEq (QName u1 _ _) (QName u2 _ _) = u1 == u2++qnEq (QName _ n1 l1) (QName _ n2 l2) = qnEq1 n1 n2 l1 l2+ where+ qnEq1 (c1:ns1) (c2:ns2) ln1 ln2 = c1==c2 && qnEq1 ns1 ns2 ln1 ln2+ qnEq1 [] ns2 ln1@(_:_) ln2 = qnEq1 ln1 ns2 [] ln2+ qnEq1 ns1 [] ln1 ln2@(_:_) = qnEq1 ns1 ln2 ln1 []+ qnEq1 [] [] [] [] = True+ qnEq1 _ _ _ _ = False+-}++{-|+Convert a filepath to a file: URI stored in a QName. If the+input file path is relative then the 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 what 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"++-}++-- since we build up the URI manually we could+-- create the QName directly, but leave that +-- for now.++qnameFromFilePath :: FilePath -> IO QName+qnameFromFilePath = fmap qnameFromURI . filePathToURI+ +emptyAuth :: Maybe URIAuth+emptyAuth = Just $ URIAuth "" "" ""++filePathToURI :: FilePath -> IO URI+filePathToURI fname = do+ ipath <- canonicalizePath fname+ + {-+ let paths = splitDirectories ipath+ txt = intercalate "/" $ case paths of+ "/":rs -> rs+ _ -> paths+ -}+ + -- Is manually creating the URI sensible?+ -- return $ fromJust $ parseURI $ "file:///" ++ txt+ -- return $ URI "file:" emptyAuth txt "" ""+ return $ URI "file:" emptyAuth ipath "" ""++--------------------------------------------------------------------------------+--+-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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+--+--------------------------------------------------------------------------------
+ src/Swish/Utils/ShowM.hs view
@@ -0,0 +1,75 @@+--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : ShowM+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 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 ShowM interface, and+--+-- (3) uses a ShowS intermediate value to avoid unnecessary+-- concatenation of long strings.+--+--------------------------------------------------------------------------------++module Swish.Utils.ShowM (ShowM(..)) where++------------------------------------------------------------+-- ShowM framework+------------------------------------------------------------++-- |ShowM is a type class for values that may be formatted in+-- multi-line displays.+class (Show sh) => ShowM 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.+ showms :: String -> sh -> ShowS+ showms _ = shows++--------------------------------------------------------------------------------+--+-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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.cabal view
@@ -1,5 +1,5 @@ Name: swish-Version: 0.6.1.2+Version: 0.6.2.1 Stability: experimental License: LGPL License-file: LICENSE @@ -9,8 +9,8 @@ Category: Semantic Web Synopsis: A semantic web toolkit. -Tested-With: GHC==6.12.3-Cabal-Version: >= 1.6+Tested-With: GHC==7.0.4+Cabal-Version: >= 1.9.2 Homepage: https://bitbucket.org/doug_burke/swish/wiki/Home Bug-reports: https://bitbucket.org/doug_burke/swish/issues @@ -46,6 +46,21 @@ . Changes: .+ [Version 0.6.2.1] Hackage did not want to upload @0.6.2.0@, so re-try by + disabling the @hpc@ and @developer@ flags for the tests to + work around what appears to be <http://hackage.haskell.org/trac/hackage/ticket/811>.+ .+ [Version 0.6.2.0] Updated "Swish.RDF.Vocabulary" and "Swish.RDF.RDFGraph" to+ include more common RDF terms. Added "Swish.RDF.Vocabulary.DublinCore",+ "Swish.RDF.Vocabulary.FOAF", and "Swish.RDF.Vocabulary.Geo" modules,+ "Swish.RDF.Vocabulary.OWL", "Swish.RDF.Vocabulary.RDF", and + "Swish.RDF.Vocabulary.XSD" modules (re-exported from "Swish.RDF.Vocabulary"+ as necessary). The test suite has been updated to take advantage of the+ support in recent Cabal versions which means that the @tests@ flag has been+ removed and the minimum Cabal version increased to @1.9.2@. A change was+ made to the internal labelling of the RDFS container-property axioms in+ "Swish.RDF.RDFProofContext".+ . [Version 0.6.1.2] Corrected minimum mtl constraint from 1 to 2 and updated the maximum time version to 1.4 from 1.3. .@@ -55,14 +70,14 @@ provided in version 0.3.2.1). . [Version 0.6.1.0] Added support for Turtle format (added the- Swish.RDF.TurtleFormatter and Swish.RDF.TurtleParser modules).+ "Swish.RDF.TurtleFormatter" and "Swish.RDF.TurtleParser" modules). . [Version 0.6.0.2] Minor internal changes. .- [Version 0.6.0.1] Moved to using hashing routine using the Data.Hashable- interface rather than Swish.Utils.MiscHelpers which is deprecated.+ [Version 0.6.0.1] Moved to using hashing routine using the @Data.Hashable@+ interface rather than "Swish.Utils.MiscHelpers", which is deprecated. .- [Version 0.6.0.0] Add Data.Interned.URI and use it to speed up the QName+ [Version 0.6.0.0] Add "Data.Interned.URI" and use it to speed up the 'QName' equality check. . [Version 0.5.0.3] Didn't get all the required @FlexibleInstances@.@@ -168,13 +183,9 @@ Description: Turn on developer flags Default: False -Flag tests- Description: Build the test programs.- Default: False--Flag hpc- Description: Use Hpc for the tests- Default: False+-- Flag hpc+-- Description: Use Hpc for the tests+-- Default: False Library Build-Depends:@@ -195,6 +206,8 @@ hashable == 1.1.*, intern == 0.8.* + Hs-Source-Dirs: src/+ Exposed-Modules: Swish Swish.RDF@@ -234,6 +247,12 @@ Swish.RDF.TurtleParser Swish.RDF.VarBinding Swish.RDF.Vocabulary+ Swish.RDF.Vocabulary.DublinCore+ Swish.RDF.Vocabulary.FOAF+ Swish.RDF.Vocabulary.Geo+ Swish.RDF.Vocabulary.OWL+ Swish.RDF.Vocabulary.RDF+ Swish.RDF.Vocabulary.XSD Swish.Utils.ListHelpers Swish.Utils.LookupMap Swish.Utils.MiscHelpers@@ -253,298 +272,416 @@ ghc-options: -Werror ghc-prof-options: -auto-all -Executable BuiltInMapTest- Main-Is: tests/BuiltInMapTest.hs- if !flag(tests)- buildable: False-- if flag(tests)- Build-Depends: - HUnit == 1.2.*-- if flag(hpc)- ghc-options: -fhpc+Test-Suite test-builtinmap+ type: exitcode-stdio-1.0+ Hs-Source-Dirs: tests/ src/+ Main-Is: BuiltInMapTest.hs+ Other-Modules: TestHelpers - if flag(developer)- ghc-options: -Werror- ghc-prof-options: -auto-all+ -- if flag(hpc)+ -- ghc-options: -fhpc+ -- + -- if flag(developer)+ -- ghc-options: -Werror+ -- ghc-prof-options: -auto-all ghc-options: -Wall -fno-warn-orphans -Executable GraphPartitionTest- Main-Is: tests/GraphPartitionTest.hs-- if !flag(tests)- buildable: False-- if flag(tests)- Build-Depends: - HUnit == 1.2.*+ Build-Depends:+ HUnit == 1.2.*,+ base >=3 && < 5,+ text == 0.11.*,+ old-locale == 1.0.*, + network >= 2.2 && < 2.4,+ hashable == 1.1.*,+ intern == 0.8.*,+ directory >= 1.0 && < 1.2,+ mtl >= 2 && < 3,+ polyparse >= 1.6 && < 1.8,+ time >= 1.1 && < 1.5 - if flag(hpc)- ghc-options: -fhpc+Test-Suite test-graphpartition+ type: exitcode-stdio-1.0+ Hs-Source-Dirs: tests/ src/+ Main-Is: GraphPartitionTest.hs+ Other-Modules: TestHelpers - if flag(developer)- ghc-options: -Werror- ghc-prof-options: -auto-all+ -- if flag(hpc)+ -- ghc-options: -fhpc+ -- + -- if flag(developer)+ -- ghc-options: -Werror+ -- ghc-prof-options: -auto-all ghc-options: -Wall -fno-warn-orphans -Executable GraphTest- Main-Is: tests/GraphTest.hs- if !flag(tests)- buildable: False-- if flag(tests)- Build-Depends: - HUnit == 1.2.*+ Build-Depends:+ HUnit == 1.2.*,+ base >=3 && < 5,+ hashable == 1.1.*,+ mtl >= 2 && < 3 - if flag(hpc)- ghc-options: -fhpc+Test-Suite test-graph+ type: exitcode-stdio-1.0+ Hs-Source-Dirs: tests/ src/+ Main-Is: GraphTest.hs+ Other-Modules: TestHelpers - if flag(developer)- ghc-options: -Werror- ghc-prof-options: -auto-all+ -- if flag(hpc)+ -- ghc-options: -fhpc+ -- + -- if flag(developer)+ -- ghc-options: -Werror+ -- ghc-prof-options: -auto-all ghc-options: -Wall -fno-warn-orphans -Executable NTTest- Main-Is: tests/NTTest.hs- if !flag(tests)- buildable: False-- if flag(tests)- Build-Depends: - HUnit == 1.2.*+ Build-Depends:+ HUnit == 1.2.*,+ base >=3 && < 5,+ hashable == 1.1.*,+ mtl >= 2 && < 3 - if flag(hpc)- ghc-options: -fhpc+Test-Suite test-nt+ type: exitcode-stdio-1.0+ Hs-Source-Dirs: tests/ src/+ Main-Is: NTTest.hs+ Other-Modules: TestHelpers - if flag(developer)- ghc-options: -Werror- ghc-prof-options: -auto-all+ -- if flag(hpc)+ -- ghc-options: -fhpc+ -- + -- if flag(developer)+ -- ghc-options: -Werror+ -- ghc-prof-options: -auto-all ghc-options: -Wall -fno-warn-orphans -Executable N3ParserTest- Main-Is: tests/N3ParserTest.hs- if !flag(tests)- buildable: False-- if flag(tests)- Build-Depends: - HUnit == 1.2.*+ Build-Depends:+ HUnit == 1.2.*,+ base >=3 && < 5,+ hashable == 1.1.*,+ mtl >= 2 && < 3,+ text == 0.11.*,+ network >= 2.2 && < 2.4,+ intern == 0.8.*,+ directory >= 1.0 && < 1.2,+ old-locale == 1.0.*,+ time >= 1.1 && < 1.5,+ polyparse >= 1.6 && < 1.8 - if flag(hpc)- ghc-options: -fhpc+Test-Suite test-n3parser+ type: exitcode-stdio-1.0+ Hs-Source-Dirs: tests/ src/+ Main-Is: N3ParserTest.hs+ Other-Modules: TestHelpers - if flag(developer)- ghc-options: -Werror- ghc-prof-options: -auto-all+ -- if flag(hpc)+ -- ghc-options: -fhpc+ -- + -- if flag(developer)+ -- ghc-options: -Werror+ -- ghc-prof-options: -auto-all ghc-options: -Wall -fno-warn-orphans -Executable N3FormatterTest- Main-Is: tests/N3FormatterTest.hs- if !flag(tests)- buildable: False-- if flag(tests)- Build-Depends: - HUnit == 1.2.*+ Build-Depends:+ HUnit == 1.2.*,+ base >=3 && < 5,+ hashable == 1.1.*,+ mtl >= 2 && < 3,+ text == 0.11.*,+ network >= 2.2 && < 2.4,+ intern == 0.8.*,+ directory >= 1.0 && < 1.2,+ old-locale == 1.0.*,+ time >= 1.1 && < 1.5,+ polyparse >= 1.6 && < 1.8 - if flag(hpc)- ghc-options: -fhpc+Test-Suite test-n3formatter+ type: exitcode-stdio-1.0+ Hs-Source-Dirs: tests/ src/+ Main-Is: N3FormatterTest.hs+ Other-Modules: TestHelpers - if flag(developer)- ghc-options: -Werror- ghc-prof-options: -auto-all+ -- if flag(hpc)+ -- ghc-options: -fhpc+ -- + -- if flag(developer)+ -- ghc-options: -Werror+ -- ghc-prof-options: -auto-all ghc-options: -Wall -fno-warn-orphans -Executable RDFDatatypeXsdIntegerTest- Main-Is: tests/RDFDatatypeXsdIntegerTest.hs- if !flag(tests)- buildable: False-- if flag(tests)- Build-Depends: - HUnit == 1.2.*+ Build-Depends:+ HUnit == 1.2.*,+ base >=3 && < 5,+ hashable == 1.1.*,+ mtl >= 2 && < 3,+ text == 0.11.*,+ network >= 2.2 && < 2.4,+ intern == 0.8.*,+ directory >= 1.0 && < 1.2,+ old-locale == 1.0.*,+ time >= 1.1 && < 1.5,+ polyparse >= 1.6 && < 1.8 - if flag(hpc)- ghc-options: -fhpc+Test-Suite test-rdfdatatypexsdinteger+ type: exitcode-stdio-1.0+ Hs-Source-Dirs: tests/ src/+ Main-Is: RDFDatatypeXsdIntegerTest.hs+ Other-Modules: TestHelpers - if flag(developer)- ghc-options: -Werror- ghc-prof-options: -auto-all+ -- if flag(hpc)+ -- ghc-options: -fhpc+ -- + -- if flag(developer)+ -- ghc-options: -Werror+ -- ghc-prof-options: -auto-all ghc-options: -Wall -fno-warn-orphans -Executable RDFGraphTest- Main-Is: tests/RDFGraphTest.hs- if !flag(tests)- buildable: False-- if flag(tests)- Build-Depends: - HUnit == 1.2.*+ Build-Depends:+ HUnit == 1.2.*,+ base >=3 && < 5,+ hashable == 1.1.*,+ mtl >= 2 && < 3,+ text == 0.11.*,+ network >= 2.2 && < 2.4,+ intern == 0.8.*,+ directory >= 1.0 && < 1.2,+ old-locale == 1.0.*,+ time >= 1.1 && < 1.5,+ polyparse >= 1.6 && < 1.8 - if flag(hpc)- ghc-options: -fhpc+Test-Suite test-rdfgraph+ type: exitcode-stdio-1.0+ Hs-Source-Dirs: tests/ src/+ Main-Is: RDFGraphTest.hs+ Other-Modules: TestHelpers - if flag(developer)- ghc-options: -Werror- ghc-prof-options: -auto-all+ -- if flag(hpc)+ -- ghc-options: -fhpc+ -- + -- if flag(developer)+ -- ghc-options: -Werror+ -- ghc-prof-options: -auto-all ghc-options: -Wall -fno-warn-orphans -Executable RDFProofContextTest- Main-Is: tests/RDFProofContextTest.hs- if !flag(tests)- buildable: False- - if flag(tests)- Build-Depends: - HUnit == 1.2.*+ Build-Depends:+ HUnit == 1.2.*,+ base >=3 && < 5,+ hashable == 1.1.*,+ mtl >= 2 && < 3,+ text == 0.11.*,+ network >= 2.2 && < 2.4,+ time >= 1.1 && < 1.5,+ old-locale == 1.0.*,+ intern == 0.8.*,+ directory >= 1.0 && < 1.2 - if flag(hpc)- ghc-options: -fhpc+Test-Suite test-rdfproofcontext+ type: exitcode-stdio-1.0+ Hs-Source-Dirs: tests/ src/+ Main-Is: RDFProofContextTest.hs+ Other-Modules: TestHelpers - if flag(developer)- ghc-options: -Werror- ghc-prof-options: -auto-all+ -- if flag(hpc)+ -- ghc-options: -fhpc+ -- + -- if flag(developer)+ -- ghc-options: -Werror+ -- ghc-prof-options: -auto-all ghc-options: -Wall -fno-warn-orphans -Executable RDFProofTest- Main-Is: tests/RDFProofTest.hs- if !flag(tests)- buildable: False- - if flag(tests)- Build-Depends: - HUnit == 1.2.*+ Build-Depends:+ HUnit == 1.2.*,+ base >=3 && < 5,+ hashable == 1.1.*,+ mtl >= 2 && < 3,+ text == 0.11.*,+ network >= 2.2 && < 2.4,+ time >= 1.1 && < 1.5,+ intern == 0.8.*,+ directory >= 1.0 && < 1.2,+ old-locale == 1.0.*,+ polyparse >= 1.6 && < 1.8 - if flag(hpc)- ghc-options: -fhpc+Test-Suite test-rdfproof+ type: exitcode-stdio-1.0+ Hs-Source-Dirs: tests/ src/+ Main-Is: RDFProofTest.hs+ Other-Modules: TestHelpers - if flag(developer)- ghc-options: -Werror- ghc-prof-options: -auto-all+ -- if flag(hpc)+ -- ghc-options: -fhpc+ -- + -- if flag(developer)+ -- ghc-options: -Werror+ -- ghc-prof-options: -auto-all ghc-options: -Wall -fno-warn-orphans -Executable RDFQueryTest- Main-Is: tests/RDFQueryTest.hs- if !flag(tests)- buildable: False-- if flag(tests)- Build-Depends: - HUnit == 1.2.*+ Build-Depends:+ HUnit == 1.2.*,+ base >=3 && < 5,+ hashable == 1.1.*,+ mtl >= 2 && < 3,+ text == 0.11.*,+ network >= 2.2 && < 2.4,+ time >= 1.1 && < 1.5,+ intern == 0.8.*,+ directory >= 1.0 && < 1.2,+ old-locale == 1.0.*,+ polyparse >= 1.6 && < 1.8 - if flag(hpc)- ghc-options: -fhpc+Test-Suite test-rdfquery+ type: exitcode-stdio-1.0+ Hs-Source-Dirs: tests/ src/+ Main-Is: RDFQueryTest.hs+ Other-Modules: TestHelpers - if flag(developer)- ghc-options: -Werror- ghc-prof-options: -auto-all+ -- if flag(hpc)+ -- ghc-options: -fhpc+ -- + -- if flag(developer)+ -- ghc-options: -Werror+ -- ghc-prof-options: -auto-all ghc-options: -Wall -fno-warn-orphans -Executable RDFRulesetTest- Main-Is: tests/RDFRulesetTest.hs- if !flag(tests)- buildable: False-- if flag(tests)- Build-Depends: - HUnit == 1.2.*+ Build-Depends:+ HUnit == 1.2.*,+ base >=3 && < 5,+ hashable == 1.1.*,+ mtl >= 2 && < 3,+ text == 0.11.*,+ network >= 2.2 && < 2.4,+ time >= 1.1 && < 1.5,+ intern == 0.8.*,+ directory >= 1.0 && < 1.2,+ old-locale == 1.0.*,+ polyparse >= 1.6 && < 1.8 - if flag(hpc)- ghc-options: -fhpc+Test-Suite test-rdfruleset+ type: exitcode-stdio-1.0+ Hs-Source-Dirs: tests/ src/+ Main-Is: RDFRulesetTest.hs+ Other-Modules: TestHelpers - if flag(developer)- ghc-options: -Werror- ghc-prof-options: -auto-all+ -- if flag(hpc)+ -- ghc-options: -fhpc+ -- + -- if flag(developer)+ -- ghc-options: -Werror+ -- ghc-prof-options: -auto-all ghc-options: -Wall -fno-warn-orphans -Executable VarBindingTest- Main-Is: tests/VarBindingTest.hs- if !flag(tests)- buildable: False-- if flag(tests)- Build-Depends: - HUnit == 1.2.*+ Build-Depends:+ HUnit == 1.2.*,+ base >=3 && < 5,+ hashable == 1.1.*,+ mtl >= 2 && < 3,+ text == 0.11.*,+ network >= 2.2 && < 2.4,+ time >= 1.1 && < 1.5,+ intern == 0.8.*,+ directory >= 1.0 && < 1.2,+ old-locale == 1.0.*,+ polyparse >= 1.6 && < 1.8 - if flag(hpc)- ghc-options: -fhpc+Test-Suite test-varbinding+ type: exitcode-stdio-1.0+ Hs-Source-Dirs: tests/ src/+ Main-Is: VarBindingTest.hs+ Other-Modules: TestHelpers - if flag(developer)- ghc-options: -Werror- ghc-prof-options: -auto-all+ -- if flag(hpc)+ -- ghc-options: -fhpc+ -- + -- if flag(developer)+ -- ghc-options: -Werror+ -- ghc-prof-options: -auto-all ghc-options: -Wall -fno-warn-orphans -Executable LookupMapTest- Main-Is: tests/LookupMapTest.hs- if !flag(tests)- buildable: False-- if flag(tests)- Build-Depends: - HUnit == 1.2.*+ Build-Depends:+ HUnit == 1.2.*,+ base >=3 && < 5,+ text == 0.11.*,+ network >= 2.2 && < 2.4,+ intern == 0.8.*,+ hashable == 1.1.*,+ directory >= 1.0 && < 1.2 - if flag(hpc)- ghc-options: -fhpc+Test-Suite test-lookupmap+ type: exitcode-stdio-1.0+ Hs-Source-Dirs: tests/ src/+ Main-Is: LookupMapTest.hs+ Other-Modules: TestHelpers - if flag(developer)- ghc-options: -Werror- ghc-prof-options: -auto-all+ -- if flag(hpc)+ -- ghc-options: -fhpc+ -- + -- if flag(developer)+ -- ghc-options: -Werror+ -- ghc-prof-options: -auto-all ghc-options: -Wall -fno-warn-orphans -Executable QNameTest- Main-Is: tests/QNameTest.hs- if !flag(tests)- buildable: False-- if flag(tests)- Build-Depends: - HUnit == 1.2.*+ Build-Depends:+ HUnit == 1.2.*,+ base >=3 && < 5 - if flag(hpc)- ghc-options: -fhpc+Test-Suite test-qname+ type: exitcode-stdio-1.0+ Hs-Source-Dirs: tests/ src/+ Main-Is: QNameTest.hs+ Other-Modules: TestHelpers - if flag(developer)- ghc-options: -Werror- ghc-prof-options: -auto-all+ -- if flag(hpc)+ -- ghc-options: -fhpc+ -- + -- if flag(developer)+ -- ghc-options: -Werror+ -- ghc-prof-options: -auto-all ghc-options: -Wall -fno-warn-orphans + Build-Depends:+ HUnit == 1.2.*,+ base >=3 && < 5,+ text == 0.11.*,+ network >= 2.2 && < 2.4,+ intern == 0.8.*,+ hashable == 1.1.*,+ directory >= 1.0 && < 1.2+ -- we do not have the data files to run this test Executable SwishTest Main-Is: tests/SwishTest.hs Executable Swish- Main-Is: src/Swish.hs+ Main-Is: SwishApp.hs+ Hs-Source-Dirs: app/ src/ Other-Modules: Paths_swish ghc-options:@@ -553,4 +690,20 @@ if flag(developer) ghc-options: -Werror ghc-prof-options: -auto-all++ Build-Depends:+ base >=3 && < 5,+ text == 0.11.*,+ binary == 0.5.*,+ containers >= 0.3 && < 0.5,+ array == 0.3.*,+ polyparse >= 1.6 && < 1.8,+ old-locale == 1.0.*, + time >= 1.1 && < 1.5,+ mtl >= 2 && < 3,+ network >= 2.2 && < 2.4,+ directory >= 1.0 && < 1.2,+ filepath >= 1.1 && < 1.3,+ hashable == 1.1.*,+ intern == 0.8.*
tests/BuiltInMapTest.hs view
@@ -45,26 +45,22 @@ , namespaceXsdType ) -import Swish.Utils.LookupMap- ( mapFindMaybe )--import Swish.Utils.ListHelpers- ( equiv )+import Swish.Utils.LookupMap (mapFindMaybe) import Test.HUnit ( Test(TestCase,TestList)- , Assertion- , assertBool, assertEqual, assertFailure- , runTestTT+ , assertEqual ) -import Control.Monad (unless)-import Data.Maybe (isJust, isNothing)+import TestHelpers (runTestSuite+ , testJust+ , testEqv2) ------------------------------------------------------------ -- Test case helpers ------------------------------------------------------------ +{- assertMember :: (Eq a, Show a) => String -> a -> [a] -> Assertion assertMember preface expected actual = unless (expected `elem` actual ) (assertFailure msg)@@ -124,9 +120,13 @@ testEqv :: (Eq a, Show a) => String -> [a] -> [a] -> Test testEqv = testMaker ListTest "Eqv" +-}+ testEqvEqv :: (Eq a, Show a) => String -> [[a]] -> [[a]] -> Test-testEqvEqv = testMaker (ListTest . map ListTest) "EqvEqv"+testEqvEqv = testEqv2+-- testEqvEqv = testMaker (ListTest . map ListTest) "EqvEqv" +{- testHasEqv :: (Eq a, Show a) => String -> [a] -> [[a]] -> Test testHasEqv lab a1 a2 = TestCase ( assertMember ("testHasEqv:"++lab) ma1 ma2 )@@ -136,6 +136,7 @@ testMaybeEqv :: (Eq a, Show a) => String -> Maybe [a] -> Maybe [a] -> Test testMaybeEqv = testMaker MaybeListTest "MaybeEqv"+-} ------------------------------------------------------------ -- Test finding built-in variable binding modifiers@@ -245,7 +246,7 @@ ] main :: IO ()-main = runTestTT allTests >> return ()+main = runTestSuite allTests {- runTestFile :: Test -> IO ()
tests/GraphPartitionTest.hs view
@@ -29,13 +29,17 @@ import Swish.Utils.ListHelpers (equiv) import Test.HUnit (Test(TestCase, TestList),- assertEqual, assertBool,- runTestTT)+ assertEqual, assertBool) +import TestHelpers (runTestSuite+ , testEq, testNe+ )+ ------------------------------------------------------------ -- Test case helpers ------------------------------------------------------------ +{- testEq :: (Eq a, Show a) => String -> a -> a -> Test testEq lab a1 a2 = TestCase ( assertEqual ("testEq:"++lab) a1 a2 )@@ -43,6 +47,7 @@ testNe :: (Eq a, Show a) => String -> a -> a -> Test testNe lab a1 a2 = TestCase ( assertBool ("testNe:"++lab) (a1 /= a2) )+-} -- Compare lists and lists of lists and Maybe lists for set equivalence: @@ -538,7 +543,7 @@ ] main :: IO ()-main = runTestTT allTests >> return ()+main = runTestSuite allTests {- runTestFile t = do
tests/GraphTest.hs view
@@ -27,8 +27,7 @@ import Test.HUnit ( Test(TestCase,TestList,TestLabel),- assertEqual, assertBool,- runTestTT )+ assertEqual, assertBool ) import Data.List (sort, elemIndex) import Data.Maybe (fromJust)@@ -50,6 +49,8 @@ ) import Swish.Utils.LookupMap (LookupEntryClass(..), makeLookupMap) +import TestHelpers (runTestSuite, testEq, testEqv)+ default ( Int ) ------------------------------------------------------------@@ -82,12 +83,18 @@ ------------------------------------------------------------ testeq :: (Show a, Eq a) => String -> a -> a -> Test+testeq = testEq+{- testeq lab req got = TestCase ( assertEqual ("test"++lab) req got )+-} testeqv :: (Show a, Eq a) => String -> [a] -> [a] -> Test+testeqv = testEqv+{- testeqv lab req got = TestCase ( assertEqual ("test"++lab) True (req `equiv` got) )+-} ------------------------------------------------------------ -- Label map and entry creation helpers@@ -379,11 +386,11 @@ testLabelEqSuite :: Test testLabelEqSuite = TestList- [ testLabelEq (testLab a b) (testEq a b) n1 n2+ [ testLabelEq (testLab a b) (nodeTest a b) n1 n2 | (a,n1) <- nodelist , (b,n2) <- nodelist ] where testLab a b = a ++ "-" ++ b- testEq a b = (a == b) ||+ nodeTest a b = (a == b) || (a,b) `elem` nodeeqlist || (b,a) `elem` nodeeqlist @@ -418,11 +425,11 @@ where testLab a b = a ++ "-" ++ b testOrd a b- | testEq a b = EQ+ | nodeTest a b = EQ | otherwise = comparing fromJust (elemIndex a nodeorder) (elemIndex b nodeorder)- testEq a b = (a == b) ||+ nodeTest a b = (a == b) || (a,b) `elem` nodeeqlist || (b,a) `elem` nodeeqlist @@ -484,11 +491,11 @@ testStmtEqSuite :: Test testStmtEqSuite = TestList- [ testStmtEq (testLab a b) (testEq a b) t1 t2+ [ testStmtEq (testLab a b) (nodeTest a b) t1 t2 | (a,t1) <- tlist , (b,t2) <- tlist ] where testLab a b = a ++ "-" ++ b- testEq a b = (a == b) ||+ nodeTest a b = (a == b) || (a,b) `elem` stmteqlist || (b,a) `elem` stmteqlist @@ -1230,11 +1237,11 @@ testGraphEqSuite :: Test testGraphEqSuite = TestLabel "testGraphEqSuite" $ TestList- [ testGraphEq (testLab ll1 ll2) (testEq ll1 ll2) gg1 gg2+ [ testGraphEq (testLab ll1 ll2) (nodeTest ll1 ll2) gg1 gg2 | (ll1,gg1) <- glist , (ll2,gg2) <- glist ] where testLab ll1 ll2 = ll1 ++ "-" ++ ll2- testEq ll1 ll2 = (ll1 == ll2) ||+ nodeTest ll1 ll2 = (ll1 == ll2) || (ll1,ll2) `elem` grapheqlist || (ll2,ll1) `elem` grapheqlist @@ -1851,7 +1858,7 @@ ] main :: IO ()-main = runTestTT allTests >> return ()+main = runTestSuite allTests {- runTestFile t = do
tests/LookupMapTest.hs view
@@ -32,15 +32,15 @@ , mapTranslateEntries, mapTranslateEntriesM ) -import Swish.Utils.ListHelpers- ( equiv )- import Data.List ( sort ) -import Test.HUnit- ( Test(TestCase,TestList)- , assertEqual, runTestTT )+import Test.HUnit ( Test(TestList) ) +import TestHelpers (runTestSuite+ , testEq+ , testEqv+ )+ ------------------------------------------------------------ -- Declare lookup entry for testing ------------------------------------------------------------@@ -70,12 +70,18 @@ ------------------------------------------------------------ testeq :: (Show a, Eq a) => String -> a -> a -> Test+testeq = testEq+{- testeq lab req got = TestCase ( assertEqual ("test"++lab) req got )+-} testeqv :: (Show a, Eq a) => String -> [a] -> [a] -> Test+testeqv = testEqv+{- testeqv lab req got = TestCase ( assertEqual ("test"++lab) True (req `equiv` got) )+-} ------------------------------------------------------------ -- LookupMap functions@@ -327,11 +333,11 @@ testMapEqSuite :: Test testMapEqSuite = TestList- [ testMapEq (testLab l1 l2) (testEq l1 l2) m1 m2+ [ testMapEq (testLab l1 l2) (nodeTest l1 l2) m1 m2 | (l1,m1) <- maplist , (l2,m2) <- maplist ] where testLab l1 l2 = l1 ++ "-" ++ l2- testEq l1 l2 = (l1 == l2) ||+ nodeTest l1 l2 = (l1 == l2) || (l1,l2) `elem` mapeqlist || (l2,l1) `elem` mapeqlist @@ -455,7 +461,7 @@ ] main :: IO ()-main = runTestTT allTests >> return ()+main = runTestSuite allTests {- runTestFile t = do
tests/N3FormatterTest.hs view
@@ -59,19 +59,17 @@ import Test.HUnit ( Test(TestCase,TestList)- , assertEqual, runTestTT )+ , assertEqual ) ---------------------------------------------------------------- Common test wrappers-------------------------------------------------------------+import TestHelpers (runTestSuite, testCompareEq) +-- Specialized equality comparisons+ testLabelEq :: String -> Bool -> RDFLabel -> RDFLabel -> Test-testLabelEq lab eq n1 n2 =- TestCase ( assertEqual ("testLabelEq:"++lab) eq (n1==n2) )+testLabelEq = testCompareEq "testLabelEq:" testGraphEq :: String -> Bool -> RDFGraph -> RDFGraph -> Test-testGraphEq lab eq gg1 gg2 =- TestCase ( assertEqual ("testGraphEq:"++lab) eq (gg1==gg2) )+testGraphEq = testCompareEq "testGraphEq:" ------------------------------------------------------------ -- Define some common values@@ -1482,7 +1480,7 @@ ] main :: IO ()-main = runTestTT allTests >> return ()+main = runTestSuite allTests {- runTestFile t = do
tests/N3ParserTest.hs view
@@ -59,7 +59,7 @@ import Swish.Utils.QName (QName, qnameFromURI) import Swish.Utils.LookupMap (LookupMap(..)) -import Test.HUnit (Test(TestCase,TestList), assertEqual, runTestTT)+import Test.HUnit (Test(TestCase,TestList), assertEqual) import Network.URI (URI, nullURI, parseURIReference) @@ -71,6 +71,16 @@ import qualified Data.Text.Lazy as L import qualified Data.Text.Lazy.Builder as B +import TestHelpers (runTestSuite, testCompareEq)++-- Specialized equality comparisons++testLabelEq :: String -> Bool -> RDFLabel -> RDFLabel -> Test+testLabelEq = testCompareEq "testLabelEq:"++testGraphEq :: String -> Bool -> RDFGraph -> RDFGraph -> Test+testGraphEq = testCompareEq "testGraphEq:"+ toURI :: String -> URI toURI s = fromMaybe (error ("Internal error: invalid uri=" ++ s)) (parseURIReference s) @@ -101,14 +111,6 @@ -- Common test wrappers ------------------------------------------------------------ -testLabelEq :: String -> Bool -> RDFLabel -> RDFLabel -> Test-testLabelEq lab eq n1 n2 =- TestCase ( assertEqual ("testLabelEq:"++lab) eq (n1==n2) )--testGraphEq :: String -> Bool -> RDFGraph -> RDFGraph -> Test-testGraphEq lab eq gg1 gg2 =- TestCase ( assertEqual ("testGraphEq:"++lab) eq (gg1==gg2) )- parseTestBase :: String -> Maybe QName -> String -> B.Builder -> RDFGraph -> String -> Test parseTestBase lbl1 mbase lbl2 inp gr er = TestList@@ -1500,7 +1502,7 @@ ] main :: IO ()-main = runTestTT allTests >> return ()+main = runTestSuite allTests -------------------------------------------------------------------------------- --
tests/NTTest.hs view
@@ -36,10 +36,12 @@ import Test.HUnit ( Test(TestCase,TestList)- , assertEqual, runTestTT )+ , assertEqual ) import qualified Data.Text.Lazy as T +import TestHelpers (runTestSuite)+ ------------------------------------------------------------ -- Parser tests ------------------------------------------------------------@@ -256,7 +258,7 @@ ] main :: IO () -main = runTestTT allTests >> return ()+main = runTestSuite allTests -------------------------------------------------------------------------------- --
tests/QNameTest.hs view
@@ -29,13 +29,18 @@ ) import Swish.Utils.Namespace (makeQNameScopedName, getQName, getScopedNameURI)-import Test.HUnit (Test(TestCase,TestList), assertEqual, runTestTT)+import Test.HUnit (Test(TestList)) import Network.URI (URI, parseURIReference) import Data.Maybe (fromJust) import qualified Data.Text as T +import TestHelpers (runTestSuite+ , testCompare+ , testCompareEq+ )+ ------------------------------------------------------------ -- Define some common values ------------------------------------------------------------@@ -69,15 +74,13 @@ qb2st2 = newQName base2 "st2" qb3st3 = newQName base3 "st3" -testIsEq :: (Show a, Eq a) => String -> String -> a -> a -> Test-testIsEq lbl1 lbl2 a b = TestCase (assertEqual (lbl1++":"++lbl2) a b)- ------------------------------------------------------------ -- QName equality tests ------------------------------------------------------------ testQNameEq :: String -> Bool -> QName -> QName -> Test-testQNameEq lbl eq n1 n2 = testIsEq "QNameEq" lbl eq (n1==n2)+testQNameEq = testCompareEq "QNameEq"+-- testQNameEq lbl eq n1 n2 = testIsEq "QNameEq" lbl eq (n1==n2) qnlist :: [(String, QName)] qnlist =@@ -145,13 +148,13 @@ ------------------------------------------------------------ testStringEq :: String -> String -> String -> Test-testStringEq = testIsEq "StringEq"+testStringEq = testCompare "StringEq" testTextEq :: String -> T.Text -> T.Text -> Test-testTextEq = testIsEq "TextEq"+testTextEq = testCompare "TextEq" testURIEq :: String -> String -> URI -> Test-testURIEq lbl uri = testIsEq "URIEq" lbl (toURI uri)+testURIEq lbl uri = testCompare "URIEq" lbl (toURI uri) testPartQNameSuite :: Test testPartQNameSuite = @@ -195,7 +198,7 @@ ------------------------------------------------------------ testMaybeQNameEq :: String -> Bool -> Maybe QName -> Maybe QName -> Test-testMaybeQNameEq lbl eq n1 n2 = testIsEq "MaybeQName" lbl eq (n1==n2)+testMaybeQNameEq lbl eq n1 n2 = testCompareEq "MaybeQName" lbl eq n1 n2 testMaybeQNameEqSuite :: Test testMaybeQNameEqSuite = @@ -217,7 +220,7 @@ ------------------------------------------------------------ testQNameLe :: String -> Bool -> QName -> QName -> Test-testQNameLe lbl le n1 n2 = testIsEq "QNameLE" lbl le (n1 <= n2)+testQNameLe lbl le n1 n2 = testCompare "QNameLE" lbl le (n1 <= n2) testQNameLeSuite :: Test testQNameLeSuite = @@ -279,8 +282,8 @@ let qn = newQName (toURI a) b in TestList- [ testIsEq lbl ":split" qn ((qnameFromURI . toURI) input)- , testIsEq lbl ":show" input (show (getQNameURI qn))+ [ testCompare lbl ":split" qn ((qnameFromURI . toURI) input)+ , testCompare lbl ":show" input (show (getQNameURI qn)) ] testSplitURISuite :: Test@@ -334,8 +337,8 @@ qn = qnameFromURI u sn = makeQNameScopedName Nothing qn in TestList- [ testIsEq "SQ:URI" lbl u (getScopedNameURI sn)- , testIsEq "SQ:Qname" lbl qn (getQName sn)+ [ testCompare "SQ:URI" lbl u (getScopedNameURI sn)+ , testCompare "SQ:Qname" lbl qn (getQName sn) ] testSNameTTSuite :: Test@@ -377,7 +380,7 @@ ] main :: IO ()-main = runTestTT allTests >> return ()+main = runTestSuite allTests {- runTestFile t = do
tests/RDFDatatypeXsdIntegerTest.hs view
@@ -54,105 +54,23 @@ import Swish.Utils.Namespace (getNamespaceURI, ScopedName, makeScopedName, makeNSScopedName) import Swish.RDF.Vocabulary (namespaceDefault) import Swish.Utils.LookupMap (LookupMap(..), mapFindMaybe)-import Swish.Utils.ListHelpers (equiv) import Test.HUnit ( Test(TestCase,TestList)- , Assertion- , assertBool, assertEqual, assertFailure- , runTestTT+ , assertFailure ) import Network.URI (URI, parseURI) -import Control.Monad (unless) import Data.Monoid (Monoid(..))-import Data.Maybe (isJust, isNothing, fromMaybe, fromJust)+import Data.Maybe (fromMaybe, fromJust) import Data.List (intersperse) import qualified Data.Text as T import qualified Data.Text.Lazy.Builder as B ---------------------------------------------------------------- Test case helpers---------------------------------------------------------------assertMember :: (Eq a, Show a) => String -> a -> [a] -> Assertion-assertMember preface expected actual =- unless (expected `elem` actual ) (assertFailure msg)- where msg = (if null preface then "" else preface ++ "\n") ++- "expected: " ++ show expected ++ "\nbut got: " ++ show actual--test :: String -> Bool -> Test-test lab bv =- TestCase ( assertBool ("test:"++lab) bv )--testEq :: (Eq a, Show a) => String -> a -> a -> Test-testEq lab a1 a2 =- TestCase ( assertEqual ("testEq:"++lab) a1 a2 )--testElem :: (Eq a, Show a) => String -> a -> [a] -> Test-testElem lab a1 as =- TestCase ( assertMember ("testElem:"++lab) a1 as )--testLe :: (Ord a, Show a) => String -> Bool -> a -> a -> Test-testLe lab eq a1 a2 =- TestCase ( assertEqual ("testLe:"++lab) eq (a1<=a2) )---- Test for Just x or Nothing--testJust :: String -> Maybe a -> Test-testJust lab av =- TestCase ( assertBool ("testJust:"++lab) (isJust av) )--testNothing :: String -> Maybe a -> Test-testNothing lab av =- TestCase ( assertBool ("testJust:"++lab) (isNothing av) )---- Compare lists and lists of lists and Maybe lists for set equivalence:--data ListTest a = ListTest [a]--instance (Eq a) => Eq (ListTest a) where- (ListTest a1) == (ListTest a2) = a1 `equiv` a2--instance (Show a) => Show (ListTest a) where- show (ListTest a) = show a--data MaybeListTest a = MaybeListTest (Maybe [a])--instance (Eq a) => Eq (MaybeListTest a) where- MaybeListTest (Just a1) == MaybeListTest (Just a2) = a1 `equiv` a2- MaybeListTest Nothing == MaybeListTest Nothing = True- _ == _ = False--instance (Show a) => Show (MaybeListTest a) where- show (MaybeListTest a) = show a--testEqv :: (Eq a, Show a) => String -> [a] -> [a] -> Test-testEqv lab a1 a2 =- TestCase ( assertEqual ("testEqv:"++lab) (ListTest a1) (ListTest a2) )--testEqvEqv :: (Eq a, Show a) => String -> [[a]] -> [[a]] -> Test-testEqvEqv lab a1 a2 =- TestCase ( assertEqual ("testEqvEqv:"++lab) ma1 ma2 )- where- ma1 = ListTest $ map ListTest a1- ma2 = ListTest $ map ListTest a2--testHasEqv :: (Eq a, Show a) => String -> [a] -> [[a]] -> Test-testHasEqv lab a1 a2 =- TestCase ( assertMember ("testHasEqv:"++lab) ma1 ma2 )- where- ma1 = ListTest a1- ma2 = map ListTest a2--testMaybeEqv :: (Eq a, Show a) => String -> Maybe [a] -> Maybe [a] -> Test-testMaybeEqv lab a1 a2 =- TestCase ( assertEqual ("testMaybeEqv:"++lab) ma1 ma2 )- where- ma1 = MaybeListTest a1- ma2 = MaybeListTest a2+import TestHelpers (runTestSuite+ , testEq, testElem, testEqv, testEqv2) ------------------------------------------------------------ -- Misc values@@ -848,7 +766,7 @@ antgr = mkGraph antstr pregrss = map (map mkGraph) prestrss in- testEqvEqv lab pregrss $ bwdApply rule antgr+ testEqv2 lab pregrss $ bwdApply rule antgr testRuleBwd lab Nothing _ _ = TestCase $ assertFailure $ "testRuleBwd:"++lab++", null rule supplied" @@ -1419,7 +1337,7 @@ ] main :: IO ()-main = runTestTT allTests >> return ()+main = runTestSuite allTests {- trules = runTestTT testDatatypeRuleSuite
tests/RDFGraphTest.hs view
@@ -73,23 +73,12 @@ import Test.HUnit ( Test(TestCase,TestList,TestLabel) , Assertion- , assertBool, assertEqual- , runTestTT )----------------------------------------------------------------- Common definitions---------------------------------------------------------------testCompare :: (Eq a, Show a) => String -> String -> a -> a -> Test-testCompare typ lab a1 a2 =- TestCase ( assertEqual (typ++lab) a1 a2 )--testCompareEq :: (Eq a, Show a) => String -> String -> Bool -> a -> a -> Test-testCompareEq typ lab eq a1 a2 =- TestCase ( assertEqual (typ++lab) eq (a1==a2) )+ , assertBool, assertEqual ) -testEq :: (Eq a, Show a) => String -> a -> a -> Test-testEq = testCompare "testEq"+import TestHelpers ( runTestSuite+ , testEq+ , testCompare+ , testCompareEq) ------------------------------------------------------------ -- Test language tag comparisons@@ -1581,7 +1570,7 @@ ] main :: IO ()-main = runTestTT allTests >> return ()+main = runTestSuite allTests {- runTestFile t = do
tests/RDFProofContextTest.hs view
@@ -50,43 +50,27 @@ import Test.HUnit ( Test(TestCase,TestList)- , assertBool, assertEqual- , runTestTT )+ , assertBool, assertEqual ) import Network.URI (URI, parseURI) import Data.Monoid (Monoid(..))-import Data.Maybe (isJust, isNothing, fromJust, fromMaybe)+import Data.Maybe (isNothing, fromJust, fromMaybe) import qualified Data.Text as T import qualified Data.Text.Lazy.Builder as B --- misc helpers--test :: String -> Bool -> Test-test lab tst = TestCase $ assertBool lab tst--testEq :: (Eq a, Show a) => String -> a -> a -> Test-testEq lab e a = TestCase $ assertEqual lab e a--testJe :: (Eq a, Show a) => String -> a -> Maybe a -> Test-testJe lab e a = TestList- [ TestCase $ assertBool lab (isJust a)- , TestCase $ assertEqual lab e (fromJust a)- ]--testJl :: (Eq a, Show a) => String -> Int -> Maybe [a] -> Test-testJl lab e a = TestList- [ TestCase $ assertBool lab (isJust a)- , TestCase $ assertEqual lab e (length (fromJust a))- ]+import TestHelpers ( runTestSuite+ , test+ , testEq+ , testElem+ ) -testNo :: (Eq a, Show a) => String -> [[a]] -> Test-testNo lab a =- TestCase $ assertBool lab (null a)+-- misc helpers testGr :: String -> RDFGraph -> [RDFGraph] -> Test-testGr lab eg a = TestCase $ assertBool lab (eg `elem` a)+testGr = testElem+-- testGr lab eg a = TestCase $ assertBool lab (eg `elem` a) -- testProof "rdfProof01" True rdfProof01 testProof :: String -> Bool -> RDFProof -> Test@@ -499,7 +483,7 @@ "ex:s1 rdf:_123 ex:o ." rdfStep08a :: RDFProofStep -rdfStep08a = makeRDFProofStep (getRule "rs_rdfs:cp1") [rdfBase08] rdfCons08a+rdfStep08a = makeRDFProofStep (getRule "rs_rdfs:cp11") [rdfBase08] rdfCons08a rdfCons08a :: RDFFormula rdfCons08a = makeFormula scopeex "rdfCons08a" @@ -675,7 +659,7 @@ ] main :: IO ()-main = runTestTT allTests >> return ()+main = runTestSuite allTests {- runTestFile t = do
tests/RDFProofTest.hs view
@@ -52,45 +52,26 @@ import Swish.RDF.Rule (Rule(..)) import Swish.Utils.Namespace (Namespace, makeNamespace, ScopedName, makeNSScopedName) -import Test.HUnit- ( Test(TestCase,TestList)- , assertBool, assertEqual- , runTestTT )+import Test.HUnit ( Test(TestList) ) import Network.URI (URI, parseURI) import Data.Monoid (Monoid(..))-import Data.Maybe (isJust, fromJust)+import Data.Maybe (fromJust) import qualified Data.Text as T import qualified Data.Text.Lazy.Builder as B --- misc helpers--test :: String -> Bool -> Test-test lab tst = TestCase $ assertBool lab tst--testEq :: (Eq a, Show a) => String -> a -> a -> Test-testEq lab e a = TestCase $ assertEqual lab e a--testJe :: (Eq a, Show a) => String -> a -> Maybe a -> Test-testJe lab e a = TestList- [ TestCase $ assertBool lab (isJust a)- , TestCase $ assertEqual lab e (fromJust a)- ]--testJl :: (Eq a, Show a) => String -> Int -> Maybe [a] -> Test-testJl lab e a = TestList- [ TestCase $ assertBool lab (isJust a)- , TestCase $ assertEqual lab e (length (fromJust a))- ]+import TestHelpers ( runTestSuite+ , test+ , testEq, testElem+ , testNo+ ) -testNo :: (Eq a, Show a) => String -> [[a]] -> Test-testNo lab a =- TestCase $ assertBool lab (null a)+-- misc helpers testIn :: (Eq a, Show a) => String -> a -> [a] -> Test-testIn lab eg a = TestCase $ assertBool lab (eg `elem` a)+testIn = testElem -- lab eg a = TestCase $ assertBool lab (eg `elem` a) mkGr :: B.Builder -> [B.Builder] -> RDFGraph mkGr pr bdy = makeRDFGraphFromN3Builder $ mconcat (pr : bdy)@@ -1066,7 +1047,7 @@ ] main :: IO ()-main = runTestTT allTests >> return ()+main = runTestSuite allTests {- runTestFile t = do
tests/RDFQueryTest.hs view
@@ -56,12 +56,8 @@ import Swish.Utils.Namespace (getNamespaceURI, ScopedName, makeScopedName) import Swish.RDF.Vocabulary (namespaceRDF, langName, swishName, rdfType, rdfXMLLiteral) import Swish.RDF.N3Parser (parseN3)-import Swish.Utils.ListHelpers (equiv) -import Test.HUnit- ( Test(TestCase,TestList)- , assertBool, assertEqual- , runTestTT )+import Test.HUnit ( Test(TestList) ) import qualified Data.Text.Lazy.Builder as B @@ -70,27 +66,22 @@ import Data.Monoid (Monoid(..)) import Data.Maybe (fromJust) +import TestHelpers (runTestSuite+ , test+ , testEq+ , testElem+ , testEqv+ )+ ------------------------------------------------------------ -- misc helpers ------------------------------------------------------------ -newtype Set a = Set [a] deriving Show--instance (Eq a) => Eq (Set a) where- Set v1 == Set v2 = v1 `equiv` v2--test :: String -> Bool -> Test-test lab tst = TestCase $ assertBool lab tst--testEq :: (Eq a, Show a) => String -> a -> a -> Test-testEq lab e a = TestCase $ assertEqual lab e a- testLs :: (Eq a, Show a) => String -> [a] -> [a] -> Test-testLs lab e a = TestCase $ assertEqual lab (Set e) (Set a)+testLs = testEqv testGr :: String -> B.Builder -> [RDFGraph] -> Test-testGr lab e a = TestCase $ assertBool lab (eg `elem` a)- where eg = graphFromBuilder mempty e+testGr lab e = testElem lab (graphFromBuilder mempty e) graphFromBuilder :: B.Builder -> B.Builder -> RDFGraph graphFromBuilder prefix body = @@ -99,20 +90,6 @@ Right gr -> gr Left msg -> error msg --- Compare lists for set equivalence:--data ListTest a = ListTest [a]--instance (Eq a) => Eq (ListTest a) where- (ListTest a1) == (ListTest a2) = a1 `equiv` a2--instance (Show a) => Show (ListTest a) where- show (ListTest a) = show a--testEqv :: (Eq a, Show a) => String -> [a] -> [a] -> Test-testEqv lab a1 a2 =- TestCase ( assertEqual ("testEqv:"++lab) (ListTest a1) (ListTest a2) )- ------------------------------------------------------------ -- test1: simple query qith URI, literal and blank nodes. ------------------------------------------------------------@@ -1674,7 +1651,7 @@ ] main :: IO ()-main = runTestTT allTests >> return ()+main = runTestSuite allTests {- runTestFile t = do
tests/RDFRulesetTest.hs view
@@ -59,8 +59,7 @@ import Test.HUnit ( Test(TestCase,TestList)- , assertBool, assertEqual- , runTestTT+ , assertBool ) import Network.URI (URI, parseURI)@@ -72,44 +71,69 @@ import qualified Data.Text as T import qualified Data.Text.Lazy.Builder as B +import TestHelpers (runTestSuite+ , test+ , testLe+ , testCompare+ , testCompareEq+ , testMaker+ )+import qualified TestHelpers as T+ ------------------------------------------------------------ -- Test case helpers ------------------------------------------------------------ -test :: String -> Bool -> Test-test lab bv =- TestCase ( assertBool ("test:"++lab) bv )- testVal :: (Eq a, Show a) => String -> a -> a -> Test+testVal = testCompare "testVal:"+{- testVal lab a1 a2 = TestCase ( assertEqual ("testVal:"++lab) a1 a2 )+-} testEq :: (Eq a, Show a) => String -> Bool -> a -> a -> Test+testEq = testCompareEq "testIsEq:"+{- testEq lab eq a1 a2 = TestCase ( assertEqual ("testEq:"++lab) eq (a1==a2) )+-} testEqual :: (Eq a, Show a) => String -> a -> a -> Test+testEqual = T.testEq+{- testEqual lab a1 a2 = TestCase ( assertEqual ("testEq:"++lab) a1 a2 )+-} +{- testLe :: (Ord a, Show a) => String -> Bool -> a -> a -> Test testLe lab eq a1 a2 = TestCase ( assertEqual ("testLe:"++lab) eq (a1<=a2) )+-} testStringEq :: String -> String -> String -> Test+testStringEq = testCompare "testStringEq:"+{- testStringEq lab s1 s2 = TestCase ( assertEqual ("testStringEq:"++lab) s1 s2 )+-} testSameNamespace :: String -> Namespace -> Namespace -> Test+testSameNamespace = testMaker getNamespaceTuple "testSameNamespace:"+{- testSameNamespace lab n1 n2 = TestCase ( assertBool ("testSameNamespace:"++lab) ((p1==p2)&&(u1==u2)) ) where (p1, u1) = getNamespaceTuple n1 (p2, u2) = getNamespaceTuple n2+-} testScopedNameEq :: String -> Bool -> ScopedName -> ScopedName -> Test+testScopedNameEq = testCompareEq "testScopedNameEq:"+{- testScopedNameEq lab eq n1 n2 = TestCase ( assertEqual ("testScopedNameEq:"++lab) eq (n1==n2) )+-} {- testQNameEq :: String -> Bool -> QName -> QName -> Test@@ -366,7 +390,7 @@ ] main :: IO ()-main = runTestTT allTests >> return ()+main = runTestSuite allTests {- runTestFile t = do
+ tests/TestHelpers.hs view
@@ -0,0 +1,245 @@+--------------------------------------------------------------------------------+-- See end of this file for licence information.+--------------------------------------------------------------------------------+-- |+-- Module : TestHelpers+-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke+-- License : GPL V2+--+-- Maintainer : Douglas Burke+-- Stability : experimental+-- Portability : H98+--+-- This module contains test case helper functions, providing a range of+-- commonly-used test cases.+--+--------------------------------------------------------------------------------++module TestHelpers+ ( + runTestSuite+ , test+ , testCompare, testCompareEq+ , testEq, testNe, testLe, testGe+ , testElem+ , testJust, testNothing + , testJe, testJl, testNo+ , testEqv, testEqv2, testHasEqv, testMaybeEqv+ , testMaker + )+ where++import System.Exit (exitSuccess, exitFailure)++import Test.HUnit (Test(TestCase, TestList)+ , runTestTT+ , Assertion+ , assertBool, assertEqual, assertFailure+ )+import Test.HUnit.Base (Counts(..))++import Control.Monad (unless)++import Data.Maybe (isJust, isNothing, fromJust)++import Swish.Utils.ListHelpers (equiv)++-- | Run the test suite and exit the process with either +-- @exitSuccess@ or @exitFailure@.+runTestSuite :: Test -> IO ()+runTestSuite tests = do+ Counts ncases ntried nerrors nfailures <- runTestTT tests+ -- It's not clear whether all these checks are needed but leave in+ if ntried == ncases && nerrors == nfailures && nerrors == 0+ then exitSuccess+ else exitFailure++------------------------------------------------------------+-- Test case helpers+------------------------------------------------------------++assertMember :: (Eq a, Show a) => String -> a -> [a] -> Assertion+assertMember preface expected actual =+ unless (expected `elem` actual ) (assertFailure msg)+ where msg = (if null preface then "" else preface ++ "\n") +++ "expected: " ++ show expected ++ "\nbut got: " ++ show actual++test :: String -> Bool -> Test+test lab = TestCase . assertBool ("test:"++lab)++testCompare :: (Eq a, Show a) => String -> String -> a -> a -> Test+testCompare typ lab a1 a2 =+ TestCase ( assertEqual (typ++lab) a1 a2 )++testCompareEq :: (Eq a, Show a) => String -> String -> Bool -> a -> a -> Test+testCompareEq typ lab eq a1 a2 =+ TestCase ( assertEqual (typ++lab) eq (a1==a2) )++testMaker :: (Show b, Eq b) => (a -> b) -> String -> String -> a -> a -> Test+testMaker conv l1 l2 x y =+ TestCase (assertEqual (l1 ++ ":" ++ l2) (conv x) (conv y))++testEq :: (Eq a, Show a) => String -> a -> a -> Test+testEq = testCompare "testEq:"++testNe :: (Eq a, Show a) => String -> a -> a -> Test+testNe lab a1 a2 =+ TestCase ( assertBool ("testNe:"++lab) (a1 /= a2) )++testLe :: (Ord a, Show a) => String -> Bool -> a -> a -> Test+testLe lab eq a1 a2 =+ TestCase ( assertEqual ("testLe:"++lab) eq (a1<=a2) )++testGe :: (Ord a, Show a) => String -> Bool -> a -> a -> Test+testGe lab eq a1 a2 =+ TestCase ( assertEqual ("testGe:"++lab) eq (a1>=a2) )++-- Test for Just x or Nothing++testJust :: String -> Maybe a -> Test+testJust lab = TestCase . assertBool ("testJust:"++lab) . isJust++testNothing :: String -> Maybe a -> Test+testNothing lab = TestCase . assertBool ("testJust:"++lab) . isNothing++testJe :: (Eq a, Show a) => String -> a -> Maybe a -> Test+testJe lab e a = TestList+ [ TestCase $ assertBool lab (isJust a)+ , TestCase $ assertEqual lab e (fromJust a)+ ]++testJl :: (Eq a, Show a) => String -> Int -> Maybe [a] -> Test+testJl lab e a = TestList+ [ TestCase $ assertBool lab (isJust a)+ , TestCase $ assertEqual lab e (length (fromJust a))+ ]++testNo :: (Eq a, Show a) => String -> [[a]] -> Test+testNo lab a =+ TestCase $ assertBool lab (null a)++-- Test for list membership++testElem :: (Eq a, Show a) => String -> a -> [a] -> Test+testElem lab a1 as = TestCase ( assertMember ("testElem:"++lab) a1 as )++-- Compare lists and lists of lists and Maybe lists for set equivalence:++data ListTest a = ListTest [a]++instance (Eq a) => Eq (ListTest a) where+ (ListTest a1) == (ListTest a2) = a1 `equiv` a2++instance (Show a) => Show (ListTest a) where+ show (ListTest a) = show a++data MaybeListTest a = MaybeListTest (Maybe [a])++instance (Eq a) => Eq (MaybeListTest a) where+ MaybeListTest (Just a1) == MaybeListTest (Just a2) = a1 `equiv` a2+ MaybeListTest Nothing == MaybeListTest Nothing = True+ _ == _ = False++instance (Show a) => Show (MaybeListTest a) where+ show (MaybeListTest a) = show a++testEqv :: (Eq a, Show a) => String -> [a] -> [a] -> Test+testEqv = testMaker ListTest "testEqv" ++testEqv2 :: (Eq a, Show a) => String -> [[a]] -> [[a]] -> Test+testEqv2 = testMaker (ListTest . map ListTest) "testEqv2"++testHasEqv :: (Eq a, Show a) => String -> [a] -> [[a]] -> Test+testHasEqv lab a1 a2 =+ TestCase ( assertMember ("testHasEqv:"++lab) ma1 ma2 )+ where+ ma1 = ListTest a1+ ma2 = map ListTest a2++testMaybeEqv :: (Eq a, Show a) => String -> Maybe [a] -> Maybe [a] -> Test+testMaybeEqv = testMaker MaybeListTest "testMaybeEqv"++{-++------------------------------------------------------------+-- Test suites for the above+------------------------------------------------------------++testSuccessSuite = TestList+ [ test "01" True+ , testEq "02" 2 2+ , testLe "03" 1 2+ , testLe "04" 2 2+ , testGe "05" 3 2+ , testGe "07" 2 2+ , testJust "08" (Just "08")+ , testNothing "09" (Nothing :: Maybe String)+ , testElem "10" 'b' "abc"+ , testEqv "11" "abc" "bca"+ , testEqv "12" "abc" "bbccaa"+ , testEqv2 "13" ["abc","def","ghi"] ["fed","ghi","bca"]+ , testHasEqv "14" "abc" ["fed","ghi","bca"]+ , testHasEqv "15" "ghi" ["fed","ghi","bca"]+ , testHasEqv "16" "def" ["fed","ghi","bca"]+ , testMaybeEqv "17" (Just "abc") (Just "bca")+ , testMaybeEqv "18" Nothing (Nothing :: Maybe String)+ ]++-- All of these tests should be failures:+-- Look for number of failures == total number of tests+testFailureSuite = TestList+ [ test "01" False+ , testEq "02" 2 22+ , testLe "03" 2 1+ , testGe "04" 2 3+ , testJust "05" (Nothing :: Maybe String)+ , testNothing "06" (Just "09")+ , testElem "07" 'd' "abc"+ , testEqv "08" "abd" "bca"+ , testEqv2 "09" ["abd","def","ghi"] ["fed","ghi","bca"]+ , testHasEqv "10" "abd" ["fed","ghi","bca"]+ , testMaybeEqv "11" (Just "abc") (Just "bda")+ , testMaybeEqv "12" Nothing (Just "bda")+ ]+-}++------------------------------------------------------------+-- All tests+------------------------------------------------------------++{-+allSuccessTests = TestList+ [ testSuccessSuite+ ]++allFailureTests = TestList+ [ testFailureSuite+ ]++mainS = runTestTT allSuccessTests+mainF = runTestTT allFailureTests+-}++--------------------------------------------------------------------------------+--+-- Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 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+--+--------------------------------------------------------------------------------
tests/VarBindingTest.hs view
@@ -33,101 +33,21 @@ , varFilterEQ, varFilterNE ) -import Swish.RDF.Vocabulary- ( swishName )--import Swish.Utils.ListHelpers- ( equiv )+import Swish.RDF.Vocabulary (swishName) -import Test.HUnit- ( Test(TestCase,TestList)- , Assertion- , assertBool, assertEqual, assertFailure- , runTestTT- )+import Test.HUnit (Test(TestList)) -import Control.Monad (unless) import Data.List (union, intersect) import Data.Maybe (isJust, isNothing, fromJust) -- import qualified Data.Text as T ---------------------------------------------------------------- Test case helpers---------------------------------------------------------------assertMember :: (Eq a, Show a) => String -> a -> [a] -> Assertion-assertMember preface expected actual =- unless (expected `elem` actual ) (assertFailure msg)- where msg = (if null preface then "" else preface ++ "\n") ++- "expected: " ++ show expected ++ "\nbut got: " ++ show actual--test :: String -> Bool -> Test-test lab bv =- TestCase ( assertBool ("test:"++lab) bv )--testEq :: (Eq a, Show a) => String -> a -> a -> Test-testEq lab a1 a2 =- TestCase ( assertEqual ("testEq:"++lab) a1 a2 )--testLe :: (Ord a, Show a) => String -> Bool -> a -> a -> Test-testLe lab eq a1 a2 =- TestCase ( assertEqual ("testLe:"++lab) eq (a1<=a2) )---- Test for Just x or Nothing--testJust :: String -> Maybe a -> Test-testJust lab av =- TestCase ( assertBool ("testJust:"++lab) (isJust av) )--testNothing :: String -> Maybe a -> Test-testNothing lab av =- TestCase ( assertBool ("testJust:"++lab) (isNothing av) )---- Compare lists and lists of lists and Maybe lists for set equivalence:--data ListTest a = ListTest [a]--instance (Eq a) => Eq (ListTest a) where- (ListTest a1) == (ListTest a2) = a1 `equiv` a2--instance (Show a) => Show (ListTest a) where- show (ListTest a) = show a--data MaybeListTest a = MaybeListTest (Maybe [a])--instance (Eq a) => Eq (MaybeListTest a) where- MaybeListTest (Just a1) == MaybeListTest (Just a2) = a1 `equiv` a2- MaybeListTest Nothing == MaybeListTest Nothing = True- _ == _ = False--instance (Show a) => Show (MaybeListTest a) where- show (MaybeListTest a) = show a--testEqv :: (Eq a, Show a) => String -> [a] -> [a] -> Test-testEqv lab a1 a2 =- TestCase ( assertEqual ("testEqv:"++lab) (ListTest a1) (ListTest a2) )--testEqvEqv :: (Eq a, Show a) => String -> [[a]] -> [[a]] -> Test-testEqvEqv lab a1 a2 =- TestCase ( assertEqual ("testEqvEqv:"++lab) ma1 ma2 )- where- ma1 = ListTest $ map ListTest a1- ma2 = ListTest $ map ListTest a2--testHasEqv :: (Eq a, Show a) => String -> [a] -> [[a]] -> Test-testHasEqv lab a1 a2 =- TestCase ( assertMember ("testHasEqv:"++lab) ma1 ma2 )- where- ma1 = ListTest a1- ma2 = map ListTest a2--testMaybeEqv :: (Eq a, Show a) => String -> Maybe [a] -> Maybe [a] -> Test-testMaybeEqv lab a1 a2 =- TestCase ( assertEqual ("testMaybeEqv:"++lab) ma1 ma2 )- where- ma1 = MaybeListTest a1- ma2 = MaybeListTest a2+import TestHelpers (runTestSuite+ , test+ , testEq + , testEqv, testEqv2, testHasEqv, testMaybeEqv+ , testJust, testNothing + ) ------------------------------------------------------------ -- Define and variable bindings@@ -499,7 +419,7 @@ vbmName (fromJust jvbmid1) , testEqv "testVarCompose01" vbm34vocab $ vbmVocab vbm34- , testEqvEqv "testVarCompose02" vbm34usage $ vbmUsage vbm34+ , testEqv2 "testVarCompose02" vbm34usage $ vbmUsage vbm34 , testMaybeEqv "testVarCompose03" (Just ["c","d"]) $ vbmCompatibility vbm34 ["a","b"] , testMaybeEqv "testVarCompose04" (Just ["b","d"]) $@@ -528,7 +448,7 @@ vbmApply vbm34 [vbad,vbbd,vbcd] , testEqv "testVarCompose21" vbm43vocab $ vbmVocab vbm43- , testEqvEqv "testVarCompose22" vbm43usage $ vbmUsage vbm43+ , testEqv2 "testVarCompose22" vbm43usage $ vbmUsage vbm43 , testMaybeEqv "testVarCompose23" Nothing $ vbmCompatibility vbm43 ["a","b"] , testMaybeEqv "testVarCompose24" (Just ["b","d"]) $@@ -560,7 +480,7 @@ , test "testVarCompose42" (isNothing vbm65) , test "testVarCompose43" (isNothing vbm78) , test "testVarCompose44" (isJust vbm87)- , testEqvEqv "testVarCompose45" vbm87usage $+ , testEqv2 "testVarCompose45" vbm87usage $ vbmUsage (fromJust vbm87) , test "testVarCompose51" $ isJust jvbm1id@@ -870,7 +790,7 @@ ] main :: IO ()-main = runTestTT allTests >> return ()+main = runTestSuite allTests {- runTestFile t = do