defaultPort :: PortNumber
-defaultPort = fromIntegral 8080
+defaultPort = toEnum 8080
defaultLocalStateDir :: FilePath
defaultLocalStateDir = LOCALSTATEDIR -- replaced by CPP
options :: [OptDescr CmdOpt]
options = [ Option ['p'] ["port"]
- (ReqArg (OptPortNum . fromIntegral . read) "NUM")
+ (ReqArg (OptPortNum . toEnum . read) "NUM")
("Port number to listen. (default: " ++ show defaultPort ++ ")")
, Option ['d'] ["localstatedir"]
Rakka.Storage.Impl
Rakka.SystemConfig
Rakka.Utils
+ Rakka.Validation
Rakka.Wiki
Rakka.Wiki.Interpreter
Rakka.Wiki.Interpreter.Base
Extensions:
Arrows, ExistentialQuantification, ScopedTypeVariables
GHC-Options:
- -Wall -Werror -XDeriveDataTypeable
+ -Wall -XDeriveDataTypeable
Executable RakkaUnitTest
if flag(build-test-suite)
module Rakka.Resource
( runIdempotentA
+ , runXmlA
, outputXmlPage
)
where
import Network.HTTP.Lucu
import Network.HTTP.Lucu.Utils
import Network.URI hiding (path)
+import Rakka.Environment
+import Rakka.Validation
+import Text.XML.HXT.Arrow.ReadDocument
import Text.XML.HXT.Arrow.WriteDocument
import Text.XML.HXT.Arrow.XmlIOStateArrow
import Text.XML.HXT.DOM.TypeDefs
rsrc
+runXmlA :: Environment -> FilePath -> IOSArrow XmlTree (Resource c) -> Resource c
+runXmlA env schemaPath a
+ = do inputA <- getInputXmlA env schemaPath
+ [rsrc] <- liftIO $ runX ( inputA
+ >>>
+ setErrorMsgHandler False fail
+ >>>
+ a
+ )
+ rsrc
+
+
+-- well-formed でない時は 400 Bad Request になり、valid でない時は 422
+-- Unprocessable Entity になる。入力の型が XML でない時は 415
+-- Unsupported Media Type を返す。
+getInputXmlA :: Environment -> FilePath -> Resource (IOSArrow b XmlTree)
+getInputXmlA env schemaPath
+ = do reader <- getInputReader
+ validator <- getValidator env schemaPath
+ return ( setErrorMsgHandler False (abort BadRequest [] . Just)
+ >>>
+ reader
+ >>>
+ setErrorMsgHandler False (abort UnprocessableEntitiy [] . Just)
+ >>>
+ validator
+ )
+
+
+getInputReader :: Resource (IOSArrow b XmlTree)
+getInputReader
+ = do mimeType <- getContentType
+ case mimeType of
+ Nothing
+ -> getFailingReader BadRequest [] (Just "Missing Content-Type")
+ Just (MIMEType "text" "xml" _)
+ -> getXmlReader
+ Just (MIMEType "application" "xml" _)
+ -> getXmlReader
+ Just t
+ -> getFailingReader UnsupportedMediaType [] (Just $ "Unsupported media type: " ++ show t)
+ where
+ getXmlReader
+ = do req <- input defaultLimit
+ return $ readString [ (a_validate , v_0)
+ , (a_check_namespaces , v_1)
+ , (a_remove_whitespace, v_0)
+ ] req
+ getFailingReader code headers msg
+ = return $ proc _ -> abortA -< (code, (headers, msg))
+
+
getEntityType :: Resource MIMEType
getEntityType
= do uri <- getRequestURI
handlePut :: Environment -> PageName -> Resource ()
-handlePut _env _name
- = do xml <- input defaultLimit
- setContentType $ read "text/xml"
- output xml
+handlePut env name
+ = runXmlA env "rakka-page-1.0.rng" $ proc tree
+ -> returnA -< do setStatus Created
loadDefaultPage name
-- ./defaultPages が存在するなら、./defaultPages/Foo を探す。無けれ
-- ば Cabal で defaultPages/Foo を探す。
- = do let pagePath = "defaultPages/" ++ encodePageName name
+ = do let pagePath = "defaultPages" </> encodePageName name
localDirExists <- doesLocalDirExist
if localDirExists then
--- /dev/null
+module Rakka.Validation
+ ( getValidator
+ )
+ where
+
+import Control.Arrow
+import Control.Arrow.ArrowList
+import Control.Arrow.ArrowTree
+import Control.Monad.Reader
+import Paths_Rakka -- Cabal が用意する。
+import Rakka.Environment
+import System.Directory
+import System.FilePath
+import Text.XML.HXT.Arrow.XmlArrow
+import Text.XML.HXT.Arrow.XmlIOStateArrow
+import Text.XML.HXT.DOM.TypeDefs
+import qualified Text.XML.HXT.RelaxNG.Schema as S
+import Text.XML.HXT.RelaxNG.Validator
+
+
+loadSchema :: FilePath -> IO (IOSArrow XmlTree XmlTree)
+loadSchema fpath
+ = do [schema] <- runX ( setErrorMsgHandler False fail
+ >>>
+ readForRelax [] fpath
+ >>>
+ perform (validateWithRelaxAndHandleErrors S.relaxSchemaArrow)
+ >>>
+ createSimpleForm [] True True True
+ >>>
+ perform ( getErrors
+ >>>
+ getChildren >>> getAttrValue "desc"
+ >>>
+ arr ("Relax NG validation: " ++ )
+ >>>
+ mkError c_err
+ >>>
+ filterErrorMsg
+ )
+ )
+ return $ validateDocumentWithRelax schema
+
+
+doesLocalDirExist :: IO Bool
+doesLocalDirExist = doesDirectoryExist "schemas"
+
+
+getValidator :: MonadIO m => Environment -> FilePath -> m (IOSArrow XmlTree XmlTree)
+getValidator _ fname
+ = liftIO $
+ do let schemaPath = "schemas" </> fname
+
+ localDirExists <- doesLocalDirExist
+ if localDirExists then
+ loadSchema schemaPath
+ else
+ getDataFileName ("schemas" </> fname) >>= loadSchema
.error:before {
content: "Error: "
}
+
+/* waiting message ************************************************************/
+.waitingMessageBoard {
+ position: fixed;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+
+ background-color: white;
+ color: black;
+ opacity: 0.7;
+
+ z-index: 1;
+}
+
+.waitingMessageBoard p {
+ text-align: center;
+ vertical-align: middle;
+ font-size: 200%;
+
+ position: fixed;
+ bottom: 1em;
+ right: 1em;
+}
</textData>
</page>
Rakka.editPage = function (baseURI, pageName) {
var $area = Rakka.switchScreen();
- $area.text("Loading... please wait.");
+
+ Rakka.displayWaitingMessage("Loading... please wait.");
// XML 版のページを取得する。
$.ajax({
url : baseURI + pageName + ".xml",
success: function (pageXml) {
+ Rakka.hideWaitingMessage();
+
var $page = $(pageXml).find("page");
var oldRevision = $page.attr("revision");
var defaultType
Rakka.displayPageEditor(baseURI, pageName, oldRevision, defaultType, source);
},
error : function (req) {
+ Rakka.hideWaitingMessage();
+
if (req.status == 404) {
Rakka.displayPageEditor(baseURI, pageName, null, "rakka", null);
}
page.appendChild(textData);
}
+ Rakka.displayWaitingMessage("Submitting... please wait.");
+
+ var url = baseURI + encodeURI(givenPageName);
$.ajax({
type : "PUT",
- url : baseURI + encodeURI(givenPageName),
+ url : url,
contentType: "text/xml",
data : doc,
- processData: false
+ processData: false,
+ success : function () {
+ window.location.replace(url);
+ },
+ error : function (req) {
+ Rakka.hideWaitingMessage();
+
+ var $area = Rakka.switchScreen();
+ $area.text("Error: " + req.status + " " + req.statusText);
+ }
});
};
$("div.body").children().show();
return null;
};
+
+ var waitingMessageBoard = null;
+
+ Rakka.displayWaitingMessage = function (msg) {
+ Rakka.hideWaitingMessage();
+
+ waitingMessageBoard
+ = $.P({className: "waitingMessageBoard"},
+ $.P({}, msg));
+
+ $("body").append(waitingMessageBoard);
+ };
+
+ Rakka.hideWaitingMessage = function () {
+ if (waitingMessageBoard != null) {
+ $(waitingMessageBoard).remove();
+ waitingMessageBoard = null;
+ }
+ };
})();