-- -*- c -*- -- format.xsl - format XML Text using XSL Transformation -- Author: klm -- License: Modified BSD stylesheet( debug := false(), indentstr := ' ', breakstr := ' ', xmldecl := 'yes', xmldecl-version := '1.0', xmldecl-encoding := 'utf-8', xmldecl-standalone, doctype-public, doctype-system, doctype-content ) version="1.0" xmlns:f="http://okowa.org/XSLT/Format" { include formatrc.xsl; output text version="1.0" omit-xml-declaration="yes" indent="no" encoding="UTF-8"; variable root := /; > match(/) { variable inside := { apply-templates f:break-inside(.); } variable child-ctx := { call f:child-context; } call f:walk( node := document('')/xsl:stylesheet/f:document/*[1], depth := 0, break := $inside, context := $child-ctx, space := { apply-templates f:space(.); }, started := false() ); -- 最終ノードの後ろに改行を入れる variable last := node()[not( text() and normalize-space() = '' )][last()]; variable last-outside := { apply-templates f:break-outside($last); } call f:break( former := $last-outside, latter := $inside ); } template f:walk( node, depth, break, context, space, started := true() ) { if($node) { variable available := { apply-templates f:available($node)( space := $space ); } variable type := { apply-templates f:display($node); } variable outside := { apply-templates f:break-outside($node); } variable next-break := { if($available) value-of($outside); else value-of($break); } if($debug) value-of(concat('[n:', name($node), ',a:', $available, ']')); if( $available = 'yes' ) { -- block モードの際には改行とインデント if($debug) value-of(concat('[s:', $started, ',p:', $space, ',c:', $context, ',t:', $type, ']')); if( $started and $space = 'default' and not( $context = 'inline' and $type = 'inline' ) ) { call f:break( former := $break, latter := $outside ); call f:indent( depth := $depth ); } -- 内容の出力 apply-templates($node)( depth := $depth, space := $space ); } -- 次 call f:walk( node := $node/following-sibling::node()[1], depth := $depth, break := $next-break, context := $type, space := $space ); } } match(f:xmldecl)( depth, space ) { text ""; } match(f:doctype)( depth, space ) { text ""; } match(f:root)( depth, space ) { call f:walk( node := $root/node()[1], depth := $depth, break := 0, context := "block", space := $space, started := false() ); } match(*)( depth, space ) { variable inside := { apply-templates f:break-inside(.); } variable depth-delta := { apply-templates f:indent(.); } variable empty := { apply-templates f:empty(.); } variable new-space := { if( $space = 'default' ) apply-templates f:space(.); else "preserve"; } variable context := { call f:child-context; } apply-templates f:begin-tag(.)( empty := $empty, depth := $depth ); call f:walk( node := node()[1], depth := $depth + number($depth-delta), break := $inside, context := $context, space := $new-space ); if( $empty = 'no' ) { -- block モードの際には改行とインデント if( $space = 'default' and not($context = 'inline') ) { variable last := node()[not( text() and normalize-space() = '' )][last()]; call f:break( former := { apply-templates f:break-outside($last); }, latter := $inside ); call f:indent( depth := $depth ); } apply-templates f:end-tag(.); } } -- 開始タグを出力 -- @param node 出力するノード -- @param empty /> で終わらせたければ真に設定 -- @param depth 属性をインデントしたければ現在の深さを指定 match f:begin-tag(*)( node := ., empty := 'no', depth ) { text "<"; value-of(name($node)); variable attr-align := { apply-templates f:attr-align($node); } -- 名前空間ノードの出力 variable ns := $node/../namespace::node(); for-each( namespace::node() ) { variable name := local-name(); variable same-ns := $ns[ local-name() = $name ]; if( $name != 'xml' and not( $same-ns and $same-ns = . ) ) { call f:attr-sep( attr-align := $attr-align, depth := $depth ); call f:xmlns; } } -- 属性ノードの出力 for-each(@*) { call f:attr-sep( attr-align := $attr-align, depth := $depth); call f:attribute; } if( $empty ="yes" ) text " /"; text ">"; } match f:begin-tag(f:comment)( node, empty, depth ) { text ""; } match f:end-tag(f:cdata)( node := . ) { text "]]>"; } match(text())( depth, space ) { choose { when( $space = 'preserve' ) call f:escape; when(not( normalize-space() = '' )) { variable text := normalize-space(concat('a', ., 'a')); call f:escape( node := substring($text, 2, string-length($text) - 2) ); } } } -- コメント match( comment() )( depth, space ) { text ""; } -- 処理命令 match( processing-instruction() )( depth, space ) { text ""; } -------------------------------------------------------- -- 現在のノードの空間 ( block / inline ) -- -> 子要素にひとつでもブロック要素が入ってればブロック空間。 template f:child-context( node := . ) { variable ret := { for-each( $node/node() ) { variable display := { apply-templates f:display(.); } if( $display = 'block' ) "1"; } }; if(string($ret)) "block"; else "inline"; } -- 有効? match f:available(/|node())(space) { "yes"; } match f:available(f:xmldecl)(space) priprity="10" { if( $xmldecl = 'yes' ) "yes"; } match f:available(f:doctype)(space) priprity="10" { if( $doctype-public or $doctype-system ) "yes"; } match f:available(f:document/text())(space) priprity="10"; match f:available(text())(space) if( $space = 'default' and normalize-space() = '' ) "no"; else "yes"; --------------------------------------------------------------- template f:escape( node := . ) { variable tmp1 := { call f:gsub( text := $node, from := '&', to := '&amp;' ); } variable tmp2 := { call f:gsub( text := $tmp1, from := '<', to := '&lt;' ); } variable tmp3 := { call f:gsub( text := $tmp2, from := '>', to := '&gt;' ); } call f:gsub( text := $tmp3, from := '"', to := '&quot;' ); } template f:gsub( text, from, to ) { if( contains($text, $from) ) { value-of(substring-before($text, $from)); value-of($to); call f:gsub( text := substring-after($text, $from), from := $from, to := $to ); } else value-of($text); } -- $indentstr を用いてインデントする -- @param depth 深さ template f:indent(depth) { if($depth > 0) { value-of($indentstr); call f:indent(depth := $depth - 1); } } -- max(before,after) 回改行する template f:break( former, latter ) { variable n-former := number($former); variable n-latter := number($latter); if($debug) value-of(concat('[b:',$n-former,'/',$n-latter,']')); if( $n-former > $n-latter ) call f:do-break(count := $n-former); else call f:do-break(count := $n-latter); } -- $breakstr を用いて改行する -- @param count 回数 template f:do-break(count := 1) { if($count > 0) { value-of($breakstr); call f:do-break(count := $count - 1); } } }