Skip to content. | Skip to navigation

Personal tools

Navigation

You are here: Home / Tips / Diazo Theme Engine

Diazo Theme Engine

Plone 佈景主題的開發方式,傳統是以 Skin 為中心,新的方式則是利用 Diazo 工具,依照 HTML 和規則檔的設定值,動態產生佈景主題和內容,甚至可以建立數個 microsite 網站。

開發 Plone 佈景主題的傳統方式侷限在 Skin 機制,自從 DeliveranceDiazo 問世之後,邁入新的里程碑,設計師不必刻意把版型放進 Skin 目錄,可以直接編輯 HTML 檔案,快速進行視覺設計,加上 Plone 4.1 開始搭配 plone.app.theming 模組,讓建立 Diazo 工作環境的門檻大幅降低,到 Plone 5 之後,Diazo 會成為預設的佈景主題工具。

技術簡介影片 投影片 What Is Diazo?Web Scraping Using DiazoUsing Variables

新的 CSS 註冊方式和注意事項

原理

Deliverance 和 Diazo 都提供 Theming Proxy 功能,它們可以透過 Rule 檔案裡的 CSS Selector 來把 CMS 的 HTML 和 Theme 進行結合。Diazo 依據 Deliverance 規格開發而成,兩者的差異在於,Diazo 內部會把 Theme 編譯成 XSL Template,而 Deliverance 則利用 Python 來處理 HTML。透過 Deliverance 的示範影片,我們可以先簡單認識它的運作方式,接著,可以閱讀模組開發文件官方技術文件流程建議文件How Diazo Works 投影片,還有推薦的模組清單,了解實務應用的狀況。

Diazo 在運作時,需要先取得設計師的 HTML 和 CSS 檔案,這些靜態資源被稱為 Theme,負責網頁外觀的設計與顯示,另一方面,動態網頁內容仍然由 Plone 負責,這些動態內容被稱為 Content,最後,由 Rule 把 Theme 和 Content 組合,再把組合後的結果傳給使用者的瀏覽器。

Rule 的作用對象是 Theme 和 Content 的 Markup Node,標定 Node 的方式,可以是 CSS3 selector 或是 XPath expression。它固定使用 rules.xml 為檔名,以 XML 格式來編輯規則內容,常見的 Rule Directive 包括 <theme />、<replace />、<drop />、<before />、<after />、<copy />,像 <merge />、<strip /> 算是偶而會用到的 Rule Directive。

技術上是以 XSLT (eXtensible Stylesheet Language Transformations) 為底層,覺得手動編輯規則檔 (rules.xml) 太麻煩嗎? 在 Plone 4.3 之後納入一個視覺編輯工具,稱為 plone.app.theming 模組,範例影片讓你先睹為快。

搭配 Plone 的場合,Diazo 通常是偵測 visual_portal_wrapper 的 CSS ID 存在與否,決定是否啟用。Diazo 甚至可以獨立安裝和運作,不侷限只和 Plone 搭配。在網址後面加上 ?diazo.off=1 選項,可以暫時取消效果。

步驟

先準備 HTML 相關資源,存取時要使用相對路徑,這樣才方便透過瀏覽器來檢視。要注意的是,舊版系統上的 libxml2 / libxslt 可能會造成無法啟動,建置過程主要在檔案系統上進行

  1. 找出 Theme 需要被取代的區塊程式碼,最好能透過獨一無二的 id 設定值來標示。
  2. 找出 Content 對應的區塊程式碼,透過 replace 或 copy 規則來測試取代效果。
  3. 找出 Content 裡需要被整批複製進 Theme 的程式碼,像 <head /> 的內容就可能符合的範例。
  4. 找出 Theme 或 Content 用不著的程式碼區塊,透過 drop 規則來刪除它們。

XPath

Firefinder 是測試工具,考慮先學習 XSLT 處理的順序

例如 <rules css:if-content="#visual-portal-wrapper"> 在 XSLT 會被展開數次,花時間進行全域搜尋,使用 XPath 的話,可以加速執行效率

# Plone 4

<rules if-content="/html/body/div[@id='visual-portal-wrapper']">

# Plone 5:

<rules if-content="/html/body[@id='visual-portal-wrapper']">

Barceloneta 的作法範例

<notheme css:if-not-content="#visual-portal-wrapper" />

Replace Content Class

規則範例

最簡化的 Rule 檔案內容:

<?xml version="1.0" encoding="UTF-8" ?>
<rules xmlns="http://namespaces.plone.org/diazo" xmlns:css="http://namespaces.plone.org/diazo/css" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <theme href="index.html" /> </rules>

<theme /> tag 裡的 href 要指定 index.html 的位置,它使用 rules.xml 的相對路徑。

實務上,需要避免 Plone 的 ZMI 介面受到影響,因此,最基本的 Rule 檔案內容會長成這樣:

<?xml version="1.0" encoding="UTF-8" ?>
<rules xmlns="http://namespaces.plone.org/diazo" xmlns:css="http://namespaces.plone.org/diazo/css" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <rules css:if-content="#visual-portal-wrapper"> <theme href="index.html" /> </rules> </rules>

css:if-content 語法是採用 CSS selector 方式,比對 Content 是否有 #visual-portal-wrapper 這個 ID,有的話就會執行 Rule 設定值。由於 #visual-portal-wrapper 只會出現在 Plone 一般網頁裡,不會出現在其他地方,利用這項技巧,就能避免影響 Plone 的管理功能。像 Google Analytics 程式碼片段,可能被置於 #visual-portal-wrapper 外面,或在 HTML BODY 裡生效,要額外處理

Theme:

<div class="row">
 <div class="span12" id="footer">
 Footer content
 </div>
</div>

Content:

<div id="portal-footer">
  <p>The <a href="http://plone.org">...</a> is under the GPL.</p>
</div>

Rule:

<replace css:theme-children="#footer"
         css:content-children="#portal-footer" />

Result:

<div class="row">
  <div class="span12" id="footer">
    <p>The <a href="http://plone.org">...</a> is under the GPL.</p>
  </div>
</div>

接著 Rule 再加上:

<after css:theme-children="#footer"
       css:content="#portal-siteactions" />

<ul id="portal-siteactions">...</ul> 會再插入在 <p></p> 之後。

另外,常見的規則設定是 <before css:theme-children="#content" css:content="#edit-bar" />,這樣會把 <div id="edit-bar">...</div> 插入在 <h1>Main content</h1> 之前。更多常見設定可參考 http://blog.dbain.com/2011/11/common-diazo-rules-for-plone.html

規則功能

<replace /> 拿 Content 的 node 內容,取代 Theme 的 node 內容。

<before /> 和 <after /> 拿 Content 的 node 內容,放到 Theme 的 node 的前面或後面。

<drop /> 可以刪除 node 內容,但要指定 Theme 或 Content 為對象。

<copy /> 拿 Content 的 node attribute 內容,複製到 Theme 的 node attribute 內容。

<merge /> 拿 Content 的 node attribute 內容,併進 Theme 的 node attribute 內容。

<strip /> 刪除 Theme 或 Content 的 node 內容,但 children 內容被留下來。

Rule 可以直接修改 Theme 內容,這時候不需要讀取 Content 內容,直接把相關的內容寫在 Rule 裡,例如:

<after theme-children="/html/head">
  <style type="text/css">
    .highlight { color: red; }
  </style>
</after>

或是利用 XSLT 來結合這樣技巧:

<replace css:theme="#details">
  <dl id="details">
    <xsl:for-each css:select="table#details > tr">
      <dt><xsl:copy-of select="td[1]/text()"/></dt>
<dd><xsl:copy-of select="td[2]/node()"/></dd>
</xsl:for-each>
<dl>
</replace>

常見的設定範例:

<rules css:if-not-content=".userrole-manager">
  <theme href="front/index.html" />
  ... your rules
</rules>
<rules css:if-content=".userrole-manager">
  <xi:include href="/++theme++barceloneta/rules.xml" />
</rules>

先 replace 再修改圖檔網址 建立 List

模組範例

Shuttle Thread Tutorial zip backup 空白字元的刪除 login page Table of Contents

Multiple Conditions Example Create Element for Each Pair of Elements

BrowserLayer with Diazo

# ZCML
layer="IDiazoMarkerInterface"

確保 JavaScript 正常執行

<after css:content='#visual-portal-wrapper script' css:theme-children='body' />

using a custom theme that does not include the base tag for the page. This results in the javascript not being able to generate the correct url for saving the sharing data. To fix this on a diazo theme, you can do something like: <before css:theme-children="head" css:content="base" /> in the theme rules

plone.app.theming

version compatibility 在 1.3.0 版本裡,你可以設定 DIAZO_ALWAYS_CACHE_RULES 變數,不為 0 的變數值會啟動快取機制

TinyMCE

TinyMCE Custom CSS Style

iframe hack

#internallinkcontainer.radioscrolllist {
  line-height: auto !important; }
#internallinkcontainer .list.item span,
#internallinkcontainer .list.item a {
  position: static !important; }

TinyMCE Edit Bar

Plone 4.3.2 TinyMCE toolbar 無法成功顯示 (tiny_mce_gzip.js 找不到) 採用 Rewrite Local URLs to Use portal_url 也無效:

#plonetheme/diazo_sunburst/theme/manifest.cfg
 [theme]
 title = Diazo Sunburst Theme
 preview = images/preview.png
+
+[theme:parameters]
+portal_url = python: portal_state.portal_url()

#plonetheme/diazo_sunburst/theme/rules.xml
+    
+      
+    

TTW Editing Rule File, with http://free-css-templates.com/ template: demo from 03:30 to 15:20

Demo Video: plonetheme.diazo_responsive

Drupal Wordpress Theme XSLT Hints plone.app.themingplugins Override Example

Transfer Changed Content into Plone Theme

Admin Site Without Theme and Share Cookie with Service Site

Pull In Content From Another Page

proxy.ini QuickStart 範例

更換 CSS Attribute 設定值,要使用 XPath expression,利用 Chrome Developer Tools 可查詢。

You can't reference a variable just anywhere, but need to do so from an XPath expression. You can avoid interfering with your replacement of the children nodes by inserting the attribute "before" the children. Here's what I would try:

<before css:theme-children=".conteudo">
    <xsl:attribute name="class">conteudo-<xsl:value-of select="$section" /></xsl:attribute>
</before>

XPath // expression will cause performance issue.

CSS Class Active

Facebook

圖檔相對路徑 想在分享時,指定顯示 logo 圖檔,可以先建立下列內容的 index.html 檔案:

<head>
  <link rel="shortcut icon" type="image/x-icon" href="images/favicon.ico" />

  <!-- facebook images -->
  <link rel="image_src" href="images/logobig.jpg" />
  <link rel="image_src" href="images/logo-square.png" />
  <link rel="image_src" href="images/logo.png" />

  <title>Welcome to Plone — Site</title>
</head>
<div id="fb-root">
<script>
  window.fbAsyncInit = function() {
    // init the FB JS SDK
    FB.init({
      status     : false,       // Check Facebook Login status
      xfbml      : true         // Look for social plugins on the page
    });
  };
  (function(d, s, id){
     var js, fjs = d.getElementsByTagName(s)[0];
     if (d.getElementById(id)) {return;}
     js = d.createElement(s); js.id = id;
     js.src = "//connect.facebook.net/en_US/all.js";
     fjs.parentNode.insertBefore(js, fjs);
   }(document, 'script', 'facebook-jssdk'));
</script>

<div class="fb-like pull-right" 

     data-href="https://www.facebook.com/mycompany"

     data-width="The pixel width of the plugin"
     data-height="The pixel height of the plugin"
     data-colorscheme="light" data-layout="button_count"
     data-action="like" data-show-faces="false" data-send="false">

建立模組

wordpress

Twitter Bootstrap Theming: XSL Styling, Transforming the edit-bar

Turn Sunburst into Bootstrap

<replace content="//div[contains(@class,'cell')]/@class">
<xsl:attribute name="class">
<xsl:if test=;contains(current(),"width-3:4")'>span9</xsl:if>
</xsl:attribute>
</replace>

Zurb Foundation: demo video

Debugger

屬性值和參數值

簡表

if-contentcss:if-content 用來標明 content 裡必須包含特定元素

css:if-content="body.template-contact-info"

直接修改 content 內容

<replace css:content="div#portal-searchbox input.searchButton">
    <button type="submit"><img src="images/search.png" alt="Search" /></button>
</replace>
<replace css:theme="div#portal-searchbox" css:content="div#portal-searchbox" />

修改 Contact Form 範例

<rules if-path="/contact-info">
  <replace css:content="#content > div > div.documentDescription">
      <div class="documentDescription">Fill in this form to book Joey!</div>
  </replace>
  <replace css:content="#content > div > p">
      p class="documentDescription"Thank you for contacting me – I will get back to you as soon as I can!</p>
  </replace>
  <replace css:content="#global_statusmessage > div.portalMessage.info">
      <div class="portalMessage info">
          <strong>Info</strong>
                An email has now been sent to Joey
          </div>
  </replace>
</rules>

parameters

common header

common diazo rules for plone

external src in <script> tag breaks rules.xml

Modifying Text

Nested List

Split Content in Columns

依照網域名稱切稱 plone.app.theming change theme selection behavior

<theme href="example.html" if="$host = 'example.com'" />

依照使用者角色切換 Change Theme Elements Based on User Role

Portlet

Move a Portlet into #content Area

Diazo themes have two levels at which they apply changes. Override Portlet Renderer

Processing bare XSL in raw mode

<replace content="div[@id='content-core']/div[@id='parent-fieldname-text']">
  <article class="post type-post status-publish format-standard hentry">
    <div class="divider-colors"></div>
    <span class="cat-links">Post</span>
    <xsl:apply-templates />
  </article>
</replace>

進入管理介面

folder_rename_form

popupforms.js Filter Unstyle

var common_content_filter =
'#content>*:not(div.configlet),dl.portalMessage.error,dl.portalMessage.info';

manifest 變數值使用大寫英文會被改成小寫 collective.sendaspdf Ignore Diazo Theme

replace vs replace children

Switch based on Cookie

多國語系文字 Multiple Language Translation

LessCSS

When Using JavaScript .prepOverlay() call to set up the AJAX overlay, it will automatically append the ajax_load flag as part of the query string used via AJAX to get the code that's displayed in the overlay.

Overlay Content

Custom based on sunburst

Site Migration Using Diazo and Funnelweb

Skins will go away but currently IThemeLayer is linked to the Skin Layer

Modify Attributes of Certain Tags Adding a Value

slot name replaced: eea.facetednavigation old main macro slot instead of content-core

Limit to Specific Users

Looping in Diazo

collective.listingviews: Listing View Customization 優缺點討論

Pulling In Content from Another Page

Workshop: Theming with Diazo

Resource Static Files

If you upload a Diazo theme as a zip, the files end up in a plone.resource resource directory within the portal_resources tool in the ZMI, and are editable there. The rules aren't in a Zope browser resource directory unless you created the theme on the filesystem and chose to put them there. plone.resource supports three different resource locations 1) ZODB, 2) global resource directory 3) packages and iterates the options in that order.

/++theme++mytheme/<subpath>
/++sitelayout++mylayout/<subpath>
/++templatelayout++mylayout/<subpath>

simplesocial like button

Edge Side Includs <esi:include>

<xi:include> 執行效率

test for the existence of a page

base tag missing will cause Password Reset issue

bootstrap theme shows up randomly

XSLT to Replace portal-personaltools

Change Theme Selection Behavior

Change Theme Based on URLs

Absolute Links

Apache + mod_transform

<Location /blog/>
  TransformSet /themes/blog.xsl
</Location>

<Location />
  TransformSet /themes/main.xsl
</Location>

不過,上述設定結果,總會優先讓 / 區段生效,變通方式是增加下列設定值:

RewriteRule ^/$ /main/ [R]

<Location /main/>
  TransformSet /themes/main.xsl
</Location>

根據文件,可能的改善方式如下:

<rules css:if-content="#blog">
  <theme href="blog.html" />
  ...
</rules>
<rules if="not(//*[@id='blog']">
  <theme href="main.html" />
  ...
</rules>
<LocationMatch "/(?!blog)">
  TransformSet /themes/main.xsl
</LocationMatch>

Attribute Error: TALES Expressions and JSON Diazo rule to do the same as pat inject

Django

Generic Theme Editor

django_diazo: 包括 Admin 介面,syncthemes 工具程式,它能利用 Dictionary 格式傳遞 View 的 Context Variable 給 Diazo。範例 django_diazo django-diazo-blog django-diazo-themes

Pyramid

pyramid.buildout/etc/diazo.ini Alex Clark's Blog SubstanceD 沒有定義 retail view

Drop Down Menu

<append content="//*[@id='portal-globalnav']"
      theme="//*[@id='nav']" />

portal_tabs

string:javascript:(function(){open('http://cgis.rchss.sinica.edu.tw/web/ruins_arc/ruins.php?k=' +document.location.pathname.split('/').pop())})()

portal_tabsstring:javascript:(function(){open('http://cgis.rchss.sinica.edu.tw/web/ruins_arc/ruins.php?k=' +document.location.pathname.split('/').pop())})()

# http://www.slideshare.net/pythonchile/web-scraping-using-diazo
from diazo.compiler import compile_theme
from lxml import etree

absolute_prefix = "/static"

rules = "rules.xml"
theme = "theme.html"

compiled_theme = compile_theme(rules, theme, absolute_prefix=absolute_prefix)
transform = etree.XSLT(compiled_theme)
content = etree.parse(some_content)
transformed = transform(content)

output = etree.tostring(transformed)

UI Elements Only For Diazo-based Site

# subscriber.py

from plone.app.theming.utils import isThemeEnabled
from zope.interface import alsoProvides
from zope.interface import Interface

class IDiazoMarkerLayer(Interface):
    """Layer Marker Interface applied if Diazo is active
    """

def apply_diazo_layer(obj, event):
    if IDiazoMarkerLayer.providedBy(event.request) \
       or not isThemeEnabled(event.request):
        return
    alsoProvides(event.request, DiazoMarkerLayer)
<subscriber
    for="Products.CMFPlone.interfaces.IPloneSiteRoot
         zope.traversing.interfaces.IBeforeTraverseEvent"
    handler=".subscriber.apply_diazo_layer" />

參考資料

PloneEDU Diazo Theming Hangout: part1, part2

repoze.plone 提供 buildout 快速建置 deliverance 環境。

svn co http://svn.repoze.org/buildouts/repoze.plone/trunk repoze.plone

cd repoze.plone

python2.4 bootstrap.py

./bin/buildout

bin/supervisord

bin/addzope2user admin admin

bin/supervisorctl stop zope

bin/paster serve etc/zope2.ini

sudo easy_install Deliverance

lxml experimental.cssselect

ZPT Fragment Support collective.themefragments 能夠讀取額外的 Browser View 內容

If a diazo theme is selected, it always wraps the json response with html tags, and it causes a js error when rendering the order overlay

plone.theme 在 Plone5 開始被併進 Products.CMFPlone 裡

<theme>
  <name>my.pkg</name>
  <enabled>false</enabled>
</theme

Fanstatic + WebOb / WSGI : automatically seeing this mimetype being returned to the browser and trying to "theme" the raw files

Regression in <replace>

<copy attributes="class" css:theme="body" css:content="body" />
<replace content="/html/body/@class"
 <xsl:attribute name="class"><xsl:value-of select="." /> brand1</xsl:attribute>
</replace>