Intelligent string break: pretty print makefile output via XSLT
I need to pretty print some output of make via XSLT (xsltproc), with a focus on easy readability of the gcc calls (because they are the most common and important lines in said output).
Situation: I have a lot of long <message>gcc -Wall ...</message>
nodes in my source XML file, one line of raw makefile output per <message>
node. The output of my XSLT sheet would be HTML, putting the contents of the <message>
nodes line by line into a <pre>
environment. I'd like to break the string preferably on any "-
" occurence (i.e. on the start of a command line parameter) -- if that is not found, on some characters (like -/.:;=
), then on any whitespace; breaking the string in the middle of a string should be the last resort. I have set a global variable $break-at
denoting the maximum characters per line.
Problem: I cannot use XSLT 2.0 functions on xsltproc (or I did not find any information regarding importing them). 开发者_如何转开发Therefore I'm stuck with substring-before()
, substring-after()
, contains()
, substring()
, string-length()
(sadly, tokenize()
is not available). I do not seem to find a way to break the string on the last occurence of whitespace (or any token) right before $break-at
.
This is an excerpt of the source XML:
<message priority="info"><![CDATA[/usr/bin/moc -DQT_NO_DEBUG -DQT_GUI_LIB -DQT_CORE_LIB -DQT_SHARED -I/usr/share/qt4/mkspecs/linux-g++ -I. -I/usr/include/qt4/QtCore -I/usr/include/qt4/QtGui -I/usr/include/qt4 -I. main.cpp -o main.moc]]></message>
<message priority="info"><![CDATA[/tmp/Akut/work/1296322206337_e01c972b8fe9b866aded56ff5dde35c3/AspectC++/bin/linux-release/ag++ -p /tmp/Akut/work/1296322206337_e01c972b8fe9b866aded56ff5dde35c3/1297456240104_09fad65d9a05790369dd919025284109_20110211213211/qt-examples --Xcompiler -c -pipe -O2 -Wall -W -D_REENTRANT -DQT_NO_DEBUG -DQT_GUI_LIB -DQT_CORE_LIB -DQT_SHARED -I/usr/share/qt4/mkspecs/linux-g++ -I. -I/usr/include/qt4/QtCore -I/usr/include/qt4/QtGui -I/usr/include/qt4 -I. -o main.o main.cpp]]></message>
<message priority="info"><![CDATA[/tmp/main.cpp_agxx_c5k6Om: In destructor 'AC::ResultBuffer<T>::~ResultBuffer() [with T = QRectF]':]]></message>
<message priority="info"><![CDATA[/tmp/main.cpp_agxx_c5k6Om:216:33: instantiated from here]]></message>
<message priority="info"><![CDATA[/tmp/main.cpp_agxx_c5k6Om:26:24: warning: dereferencing type-punned pointer will break strict-aliasing rules]]></message>
This is the calling XSLT code, calling the break-string
template on every line of output:
<pre>
<xsl:for-each select="message">
<xsl:call-template name="break-string">
<xsl:with-param name="string" select="./text()"/>
</xsl:call-template>
</xsl:for-each>
</pre>
This is my break-string
template so far -- not satisfactory yet!
<xsl:template name="break-string">
<xsl:param name="string" />
<xsl:choose>
<xsl:when test="string-length($string) <= $break-at">
<xsl:value-of select="$string"/><xsl:text>
</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test="contains($string, ' -')">
<xsl:variable name="out" select="substring-before($string, ' -')"/>
<xsl:choose>
<xsl:when test="string-length($out) <= $break-at">
<xsl:value-of select="$out"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="break-string">
<xsl:with-param name="string"
select="$out" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
<xsl:text>
</xsl:text>
<span class="indent"><xsl:text><![CDATA[ ]]></xsl:text></span><xsl:text>-</xsl:text>
<xsl:call-template name="break-string">
<xsl:with-param name="string"
select="substring-after($string, ' -')" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring($string, 1, $break-at)" /><xsl:text>
</xsl:text>
<span class="indent"><xsl:text><![CDATA[ ]]></xsl:text></span>
<xsl:call-template name="break-string">
<xsl:with-param name="string"
select="substring($string, $break-at + 1)" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
What's the most elegant method to break these strings?
Adding my own without extensions:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="pSeparators" select="'
	 -/.:;='"/>
<xsl:param name="pMaxLength" select="64"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="message">
<pre>
<xsl:call-template name="tokenize"/>
</pre>
</xsl:template>
<xsl:template name="tokenize">
<xsl:param name="pString" select="string()"/>
<xsl:param name="pMask"
select="translate(.,translate(.,$pSeparators,''),'')"/>
<xsl:param name="pLength" select="0"/>
<xsl:param name="pTail"/>
<xsl:if test="$pString">
<xsl:variable name="vSeparator"
select="substring($pMask,1,1)"/>
<xsl:variable name="vString"
select="concat(
$pTail,
substring-before(
$pString,
$vSeparator
),
substring(
$pString,
1 div not($vSeparator)
)
)"/>
<xsl:variable name="vLength"
select="string-length($vString)"/>
<xsl:variable name="vMaxLength"
select="$pMaxLength - $pLength"/>
<xsl:choose>
<xsl:when test="$vMaxLength >= $vLength">
<xsl:value-of select="$vString"/>
<xsl:call-template name="tokenize">
<xsl:with-param name="pString"
select="substring(
substring-after($pString,$vSeparator),
1 div boolean($vSeparator))"/>
<xsl:with-param name="pMask"
select="substring($pMask,2)"/>
<xsl:with-param name="pLength"
select="$pLength + $vLength"/>
<xsl:with-param name="pTail" select="$vSeparator"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="string-length(
normalize-space($vString)
) > $pMaxLength">
<xsl:value-of
select="concat(
substring(
$vString, 1, $vMaxLength
),
'
'
)"/>
<xsl:call-template name="tokenize">
<xsl:with-param name="pString"
select="substring(
$pString,
$vMaxLength - string-length($pTail) +1
)"/>
<xsl:with-param name="pMask" select="$pMask"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat('
',
normalize-space($vString))"/>
<xsl:call-template name="tokenize">
<xsl:with-param name="pString"
select="substring(
substring-after($pString,$vSeparator),
1 div boolean($vSeparator))"/>
<xsl:with-param name="pMask"
select="substring($pMask,2)"/>
<xsl:with-param name="pLength" select="$vLength"/>
<xsl:with-param name="pTail" select="$vSeparator"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
With this input:
<t>
<message priority="info"><![CDATA[/usr/bin/moc -DQT_NO_DEBUG -DQT_GUI_LIB -DQT_CORE_LIB -DQT_SHARED -I/usr/share/qt4/mkspecs/linux-g++ -I. -I/usr/include/qt4/QtCore -I/usr/include/qt4/QtGui -I/usr/include/qt4 -I. main.cpp -o main.moc]]></message>
<message priority="info"><![CDATA[/tmp/Akut/work/1296322206337_e01c972b8fe9b866aded56ff5dde35c3/AspectC++/bin/linux-release/ag++ -p /tmp/Akut/work/1296322206337_e01c972b8fe9b866aded56ff5dde35c3/1297456240104_09fad65d9a05790369dd919025284109_20110211213211/qt-examples --Xcompiler -c -pipe -O2 -Wall -W -D_REENTRANT -DQT_NO_DEBUG -DQT_GUI_LIB -DQT_CORE_LIB -DQT_SHARED -I/usr/share/qt4/mkspecs/linux-g++ -I. -I/usr/include/qt4/QtCore -I/usr/include/qt4/QtGui -I/usr/include/qt4 -I. -o main.o main.cpp]]></message>
<message priority="info"><![CDATA[/tmp/main.cpp_agxx_c5k6Om: In destructor 'AC::ResultBuffer<T>::~ResultBuffer() [with T = QRectF]':]]></message>
<message priority="info"><![CDATA[/tmp/main.cpp_agxx_c5k6Om:216:33: instantiated from here]]></message>
<message priority="info"><![CDATA[/tmp/main.cpp_agxx_c5k6Om:26:24: warning: dereferencing type-punned pointer will break strict-aliasing rules]]></message>
</t>
Output:
<t><pre>/usr/bin/moc -DQT_NO_DEBUG -DQT_GUI_LIB -DQT_CORE_LIB
-DQT_SHARED -I/usr/share/qt4/mkspecs/linux-g++ -I. -I/usr
/include/qt4/QtCore -I/usr/include/qt4/QtGui -I/usr/include/qt4
-I. main.cpp -o main.moc</pre><pre>/tmp/Akut/work/1296322206337_e01c972b8fe9b866aded56ff5dde35c3
/AspectC++/bin/linux-release/ag++ -p /tmp/Akut/work
/1296322206337_e01c972b8fe9b866aded56ff5dde35c3
/1297456240104_09fad65d9a05790369dd919025284109_20110211213211
/qt-examples --Xcompiler -c -pipe -O2 -Wall -W -D_REENTRANT
-DQT_NO_DEBUG -DQT_GUI_LIB -DQT_CORE_LIB -DQT_SHARED -I/usr
/share/qt4/mkspecs/linux-g++ -I. -I/usr/include/qt4/QtCore -I
/usr/include/qt4/QtGui -I/usr/include/qt4 -I. -o main.o main.cpp</pre><pre>/tmp/main.cpp_agxx_c5k6Om: In destructor 'AC::ResultBuffer<T>:
:~ResultBuffer() [with T = QRectF]'</pre><pre>/tmp/main.cpp_agxx_c5k6Om:216:33: instantiated from here</pre><pre>/tmp/main.cpp_agxx_c5k6Om:26:24: warning: dereferencing type
-punned pointer will break strict-aliasing rules</pre></t>
Rendered:
/usr/bin/moc -DQT_NO_DEBUG -DQT_GUI_LIB -DQT_CORE_LIB -DQT_SHARED -I/usr/share/qt4/mkspecs/linux-g++ -I. -I/usr /include/qt4/QtCore -I/usr/include/qt4/QtGui -I/usr/include/qt4 -I. main.cpp -o main.moc
/tmp/Akut/work/1296322206337_e01c972b8fe9b866aded56ff5dde35c3 /AspectC++/bin/linux-release/ag++ -p /tmp/Akut/work /1296322206337_e01c972b8fe9b866aded56ff5dde35c3 /1297456240104_09fad65d9a05790369dd919025284109_20110211213211 /qt-examples --Xcompiler -c -pipe -O2 -Wall -W -D_REENTRANT -DQT_NO_DEBUG -DQT_GUI_LIB -DQT_CORE_LIB -DQT_SHARED -I/usr /share/qt4/mkspecs/linux-g++ -I. -I/usr/include/qt4/QtCore -I /usr/include/qt4/QtGui -I/usr/include/qt4 -I. -o main.o main.cpp
/tmp/main.cpp_agxx_c5k6Om: In destructor 'AC::ResultBuffer<T>: :~ResultBuffer() [with T = QRectF]'
/tmp/main.cpp_agxx_c5k6Om:216:33: instantiated from here
/tmp/main.cpp_agxx_c5k6Om:26:24: warning: dereferencing type -punned pointer will break strict-aliasing rules
Note: The basic is the multi delimiters tokenizetion "tailing" the delimiter (because it looks like you want to keep the delimiter with the next item). I didn't give too much time, but the breaking logic migth be simplify further. Now there are three cases: item fits in current line, item doesn't fit in full line (break item), item doesn't fit in current line but it fits in new line (new line, don't break item).
You can use the existing str-split-to-lines
template from FXSL:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"
xmlns:str-split2lines-func="f:str-split2lines-func"
exclude-result-prefixes="xsl ext str-split2lines-func"
>
<xsl:import href="dvc-str-foldl.xsl"/>
<str-split2lines-func:str-split2lines-func/>
<xsl:output indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:call-template name="str-split-to-lines">
<xsl:with-param name="pStr" select="/*"/>
<xsl:with-param name="pLineLength" select="64"/>
<xsl:with-param name="pDelimiters" select="' 	 '"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="str-split-to-lines">
<xsl:param name="pStr"/>
<xsl:param name="pLineLength" select="60"/>
<xsl:param name="pDelimiters" select="' 	 '"/>
<xsl:variable name="vsplit2linesFun"
select="document('')/*/str-split2lines-func:*[1]"/>
<xsl:variable name="vrtfParams">
<delimiters><xsl:value-of select="$pDelimiters"/></delimiters>
<lineLength><xsl:copy-of select="$pLineLength"/></lineLength>
</xsl:variable>
<xsl:variable name="vResult">
<xsl:call-template name="dvc-str-foldl">
<xsl:with-param name="pFunc" select="$vsplit2linesFun"/>
<xsl:with-param name="pStr" select="$pStr"/>
<xsl:with-param name="pA0" select="ext:node-set($vrtfParams)"/>
</xsl:call-template>
</xsl:variable>
<xsl:for-each select="ext:node-set($vResult)/line">
<xsl:for-each select="word">
<xsl:value-of select="concat(., ' ')"/>
</xsl:for-each>
<xsl:value-of select="'
'"/>
</xsl:for-each>
</xsl:template>
<xsl:template match="str-split2lines-func:*">
<xsl:param name="arg1" select="/.."/>
<xsl:param name="arg2"/>
<xsl:copy-of select="$arg1/*[position() < 3]"/>
<xsl:copy-of select="$arg1/line[position() != last()]"/>
<xsl:choose>
<xsl:when test="contains($arg1/*[1], $arg2)">
<xsl:if test="string($arg1/word)">
<xsl:call-template name="fillLine">
<xsl:with-param name="pLine" select="$arg1/line[last()]"/>
<xsl:with-param name="pWord" select="$arg1/word"/>
<xsl:with-param name="pLineLength" select="$arg1/*[2]"/>
</xsl:call-template>
</xsl:if>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="$arg1/line[last()]"/>
<word><xsl:value-of select="concat($arg1/word, $arg2)"/></word>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- Test if the new word fits into the last line -->
<xsl:template name="fillLine">
<xsl:param name="pLine" select="/.."/>
<xsl:param name="pWord" select="/.."/>
<xsl:param name="pLineLength" />
<xsl:variable name="vnWordsInLine" select="count($pLine/word)"/>
<xsl:variable name="vLineLength"
select="string-length($pLine) + $vnWordsInLine"/>
<xsl:choose>
<xsl:when test="not($vLineLength + string-length($pWord)
>
$pLineLength)">
<line>
<xsl:copy-of select="$pLine/*"/>
<xsl:copy-of select="$pWord"/>
</line>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="$pLine"/>
<line>
<xsl:copy-of select="$pWord"/>
</line>
<word/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on this XML document (your XML's first element):
<message priority="info"><![CDATA[/usr/bin/moc -DQT_NO_DEBUG -DQT_GUI_LIB -DQT_CORE_LIB -DQT_SHARED -I/usr/share/qt4/mkspecs/linux-g++ -I. -I/usr/include/qt4/QtCore -I/usr/include/qt4/QtGui -I/usr/include/qt4 -I. main.cpp -o main.moc]]></message>
the wanted, correct result (split to lines with max. length 64) is produced:
/usr/bin/moc -DQT_NO_DEBUG -DQT_GUI_LIB -DQT_CORE_LIB
-DQT_SHARED -I/usr/share/qt4/mkspecs/linux-g++ -I.
-I/usr/include/qt4/QtCore -I/usr/include/qt4/QtGui
-I/usr/include/qt4 -I. main.cpp -o
精彩评论