如何在 Hakyll 中相对化 css 文件中的 URL?
How to relativize URLs in css files in Hakyll?
在我的 Hakyll 站点中,我有一个链接到页面的样式表:
<link rel="stylesheet" type="text/css" href="/css/my.css">
此 CSS 包含链接到字体文件的 @font-face
指令:
@font-face {
font-family: "Bla";
src: url("/data/bla.ttf") format("truetype");
}
问题是字体的 URL 没有被 relativizeUrls
相对化,即使我将它移动到页面本身的 <script>
标签中。如何解决这个问题?
Hakyll 的 relativizeURLs
使用 TagSoup
来解析和美化打印 HTML,因此它只能在 HTML 属性中找到的 URL 起作用。我不知道有任何现有功能可以将其扩展到 CSS 而不仅仅是 HTML 属性。
相关代码遍历 TagSoup
解析的每个标签,并将函数应用于它识别为 URLs:
的属性
-- | Apply a function to each URL on a webpage
withUrls :: (String -> String) -> String -> String
withUrls f = withTags tag
where
tag (TS.TagOpen s a) = TS.TagOpen s $ map attr a
tag x = x
attr (k, v) = (k, if isUrlAttribute k then f v else v)
(来自 Hakyll.Web.HTML
)
无法通过提供的 relativizeURLs
编译器更改此遍历逻辑,因此您可能必须自己编写。幸运的是,它非常简单:它获取站点根目录(使用 toSiteRoot
),然后使用 withURLs
将函数应用于每个 URL,将绝对路径转换为相对路径。
relativizeUrls item = do
route <- getRoute $ itemIdentifier item
return $ case route of
Nothing -> item
Just r -> fmap (relativizeUrlsWith $ toSiteRoot r) item
relativizeUrlsWith root = withUrls rel
where
isRel x = "/" `isPrefixOf` x && not ("//" `isPrefixOf` x)
rel x = if isRel x then root ++ x else x
(摘自 Hakyll.Web.RelativizeURLs
)。
您需要将此类过程与某种轻量级 CSS 解析器相结合。它看起来像这样(伪代码):
relativizeCssUrls root = renderCSS . fmap relativize . parseCSS
where relativize (URL url)
| isRel url = URL (root <> url)
| otherwise = URL url
relativize other = other
我没有使用过任何 CSS parsing/printing 库,所以我不能在这里给你一个好的建议,但是 css-text 似乎是一个不错的起点。
我走了一条更简单的路。我使用此代码获取相对于当前项目的根路径:
rootPath :: Compiler String
rootPath = (toSiteRoot . fromJust) <$> (getUnderlying >>= getRoute)
然后创建了一个 Context
具有常量字段:
fontCtx = do
root <- rootPath
return $ constField "fontRoot" root
最后,我将 @font-face
子句从 CSS 文件移到 HTML 文件中,并在那里使用了我的字段:
<style type="text/css">
@font-face {
...
src: url("$fontRoot$/data/bla.ttf") format("truetype");
}
</style>
事实证明,该上下文字段在其他地方非常有用,例如 Javascript 代码中的路径字符串,我也使用它。
tl;dr – 您可以使用 Beerend Lauwers 的 hakyll-extra
包(hackage
似乎还没有),它提供了一个 relativizeUrl
宏。或者,按如下方式实现您自己的:
如果您没有太多链接,并且不想仅仅为了执行此操作而引入 CSS 解析器,您可以创建一个函数字段 - 实际上是一个宏 - 它允许您打电话,例如relativize("/some/url")
来自页面内。 (我 运行 遇到了类似的问题,因为我想将样式表的链接相对化,仅供旧版本的 Internet Explorer 使用;对于 TagSoup
,链接看起来好像在评论中,所以它没有处理它们。)
首先,我们需要编写一个 relativizeUrls
版本,它只对单个 URL:
进行操作
import Data.List as L
-- | Relativize URL. Same logic as "relativizeUrlsWith" in
-- Hakyll.Web.Html.RelativizeUrls, but for just one url.
relativizeUrl :: String -- ^ Path to the site root
-> String -- ^ link to relativize
-> String -- ^ Resulting link
relativizeUrl root = rel
where
isRel :: String -> Bool
isRel x = "/" `L.isPrefixOf` x && not ("//" `L.isPrefixOf` x)
rel x = if isRel x then root ++ x else x
然后,我们定义一个"function field"可以添加到上下文中。
import Data.Maybe (maybe)
-- ugh. ugly name.
relativizeFuncField :: Context a
relativizeFuncField = functionField "relativize" relativize
where
relativize :: [String] -> Item a -> Compiler String
relativize args item = do
siteRoot <- getRoot <$> (getRoute $ itemIdentifier item)
arg <- case args of
[arg] -> return arg
_ -> error "relativize: expected only 1 arg"
return $ relativizeUrl siteRoot arg
getRoot :: Maybe String -> String
getRoot = maybe (error "relativize: couldn't get route") toSiteRoot
然后,在任何你想使用这个宏的地方,而不是使用,比如说,defaultContext
,使用relativizeFuncField <> defaultContext
。例如:
import Data.Monoid( (<>) )
main =
-- ...
match (fromList ["about.rst", "contact.markdown"]) $ do
route $ setExtension "html"
compile $ pandocCompiler
>>= loadAndApplyTemplate "templates/default.html" (relativizeFuncField <> defaultContext)
>>= relativizeUrls
所以,最后,这意味着在文件中,您可以在 TagSoup
尚未相对化链接的任何位置写入 $relativize("/path/to/file")$
。
希望有用:)
(这是使用 Hakyll 4.9.0.0,但我假设其他 4.X 版本大致相同。)
编辑:p.s.,非常感谢 Beerend Lauwers,他在他的 post here
中解释了 Hakyll 函数字段
再次编辑:哦。我没有看到 Beerend 实际上已经在他的 hakyll-extra
包中放置了一个 relativizeUrl
函数。
在我的 Hakyll 站点中,我有一个链接到页面的样式表:
<link rel="stylesheet" type="text/css" href="/css/my.css">
此 CSS 包含链接到字体文件的 @font-face
指令:
@font-face {
font-family: "Bla";
src: url("/data/bla.ttf") format("truetype");
}
问题是字体的 URL 没有被 relativizeUrls
相对化,即使我将它移动到页面本身的 <script>
标签中。如何解决这个问题?
Hakyll 的 relativizeURLs
使用 TagSoup
来解析和美化打印 HTML,因此它只能在 HTML 属性中找到的 URL 起作用。我不知道有任何现有功能可以将其扩展到 CSS 而不仅仅是 HTML 属性。
相关代码遍历 TagSoup
解析的每个标签,并将函数应用于它识别为 URLs:
-- | Apply a function to each URL on a webpage
withUrls :: (String -> String) -> String -> String
withUrls f = withTags tag
where
tag (TS.TagOpen s a) = TS.TagOpen s $ map attr a
tag x = x
attr (k, v) = (k, if isUrlAttribute k then f v else v)
(来自 Hakyll.Web.HTML
)
无法通过提供的 relativizeURLs
编译器更改此遍历逻辑,因此您可能必须自己编写。幸运的是,它非常简单:它获取站点根目录(使用 toSiteRoot
),然后使用 withURLs
将函数应用于每个 URL,将绝对路径转换为相对路径。
relativizeUrls item = do
route <- getRoute $ itemIdentifier item
return $ case route of
Nothing -> item
Just r -> fmap (relativizeUrlsWith $ toSiteRoot r) item
relativizeUrlsWith root = withUrls rel
where
isRel x = "/" `isPrefixOf` x && not ("//" `isPrefixOf` x)
rel x = if isRel x then root ++ x else x
(摘自 Hakyll.Web.RelativizeURLs
)。
您需要将此类过程与某种轻量级 CSS 解析器相结合。它看起来像这样(伪代码):
relativizeCssUrls root = renderCSS . fmap relativize . parseCSS
where relativize (URL url)
| isRel url = URL (root <> url)
| otherwise = URL url
relativize other = other
我没有使用过任何 CSS parsing/printing 库,所以我不能在这里给你一个好的建议,但是 css-text 似乎是一个不错的起点。
我走了一条更简单的路。我使用此代码获取相对于当前项目的根路径:
rootPath :: Compiler String
rootPath = (toSiteRoot . fromJust) <$> (getUnderlying >>= getRoute)
然后创建了一个 Context
具有常量字段:
fontCtx = do
root <- rootPath
return $ constField "fontRoot" root
最后,我将 @font-face
子句从 CSS 文件移到 HTML 文件中,并在那里使用了我的字段:
<style type="text/css">
@font-face {
...
src: url("$fontRoot$/data/bla.ttf") format("truetype");
}
</style>
事实证明,该上下文字段在其他地方非常有用,例如 Javascript 代码中的路径字符串,我也使用它。
tl;dr – 您可以使用 Beerend Lauwers 的 hakyll-extra
包(hackage
似乎还没有),它提供了一个 relativizeUrl
宏。或者,按如下方式实现您自己的:
如果您没有太多链接,并且不想仅仅为了执行此操作而引入 CSS 解析器,您可以创建一个函数字段 - 实际上是一个宏 - 它允许您打电话,例如relativize("/some/url")
来自页面内。 (我 运行 遇到了类似的问题,因为我想将样式表的链接相对化,仅供旧版本的 Internet Explorer 使用;对于 TagSoup
,链接看起来好像在评论中,所以它没有处理它们。)
首先,我们需要编写一个 relativizeUrls
版本,它只对单个 URL:
import Data.List as L
-- | Relativize URL. Same logic as "relativizeUrlsWith" in
-- Hakyll.Web.Html.RelativizeUrls, but for just one url.
relativizeUrl :: String -- ^ Path to the site root
-> String -- ^ link to relativize
-> String -- ^ Resulting link
relativizeUrl root = rel
where
isRel :: String -> Bool
isRel x = "/" `L.isPrefixOf` x && not ("//" `L.isPrefixOf` x)
rel x = if isRel x then root ++ x else x
然后,我们定义一个"function field"可以添加到上下文中。
import Data.Maybe (maybe)
-- ugh. ugly name.
relativizeFuncField :: Context a
relativizeFuncField = functionField "relativize" relativize
where
relativize :: [String] -> Item a -> Compiler String
relativize args item = do
siteRoot <- getRoot <$> (getRoute $ itemIdentifier item)
arg <- case args of
[arg] -> return arg
_ -> error "relativize: expected only 1 arg"
return $ relativizeUrl siteRoot arg
getRoot :: Maybe String -> String
getRoot = maybe (error "relativize: couldn't get route") toSiteRoot
然后,在任何你想使用这个宏的地方,而不是使用,比如说,defaultContext
,使用relativizeFuncField <> defaultContext
。例如:
import Data.Monoid( (<>) )
main =
-- ...
match (fromList ["about.rst", "contact.markdown"]) $ do
route $ setExtension "html"
compile $ pandocCompiler
>>= loadAndApplyTemplate "templates/default.html" (relativizeFuncField <> defaultContext)
>>= relativizeUrls
所以,最后,这意味着在文件中,您可以在 TagSoup
尚未相对化链接的任何位置写入 $relativize("/path/to/file")$
。
希望有用:)
(这是使用 Hakyll 4.9.0.0,但我假设其他 4.X 版本大致相同。)
编辑:p.s.,非常感谢 Beerend Lauwers,他在他的 post here
中解释了 Hakyll 函数字段再次编辑:哦。我没有看到 Beerend 实际上已经在他的 hakyll-extra
包中放置了一个 relativizeUrl
函数。