Extra xml tag when using Lib:XSLT to create text file - perl

Cross-post http://perlmonks.org/index.pl?node_id=979710
I'm trying to create a text file from some XML using Perl and Lib::XSLT, my transformation works fine except Lib::XSLT adds an unwanted ?xml version tag to the start of the file, how can I stop it doing this?
Here's my XSLT:
<xslt:stylesheet version="1.0" xmlns:data="http://www.SDMX.org/resources/SDMXML/schemas/v2_0/generic" xmlns:xslt="http://www.w3.org/1999/XSL/Transform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:message="http://www.SDMX.org/resources/SDMXML/schemas/v2_0/message" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" omit-xml-declaration="yes"/>
<xslt:param name="sep">|</xslt:param>
<xslt:output method="text" />
<xslt:template match="message:MessageGroup">
<xslt:for-each select="data:DataSet">
<!-- get dimensions (but not time) and store in dimensions variable -->
<xslt:for-each select="data:Series">
<xslt:variable name="dimensions">
<xslt:for-each select="data:SeriesKey">
<xslt:for-each select="data:Value">
<xslt:value-of select="#value" />
<xslt:value-of select="$sep" />
</xslt:for-each>
</xslt:for-each>
</xslt:variable>
<!--get obs statuses and store in obs statuses variable-->
<xslt:variable name="obsStatuses">
<xslt:for-each select="data:Attributes">
<xslt:for-each select="data:Value">
<xslt:value-of select="#value" />
</xslt:for-each>
</xslt:for-each>
</xslt:variable>
<!--write out dimensions variable, time, observation, obsstatuses variable-->
<xslt:for-each select="data:Obs">
<xslt:value-of select="$dimensions" />
<xslt:value-of select="data:Time" />
<xslt:value-of select="$sep" />
<xslt:value-of select="data:ObsValue/#value" />
<xslt:value-of select="$sep" />
<xslt:value-of select="data:Attributes/data:Value/#value"/>
<xslt:text>
</xslt:text>
</xslt:for-each>
</xslt:for-each>
</xslt:for-each>
</xslt:template>
</xslt:stylesheet>
Here's the Perl:
use Lib::XSLT;
my $parser = XML::LibXML->new();
my $xslt = XML::LibXSLT->new();
my $source = XML::LibXML->load_xml(location => "$xmlFile");
my $style_doc = $parser->parse_file(Path::Class::File->new("$xsltFile"));
my $stylesheet = $xslt->parse_stylesheet($style_doc);
open OUTPUTFILE, ">>$outputFile" or die("Unable to open $outputFile, $!");
print OUTPUTFILE $stylesheet->transform($source);
close OUTPUTFILE;

Storing the result of $stylesheet->transform() and using $stylesheet->output_file() fixes this issue, e.g:
use Lib::XSLT;
my $parser = XML::LibXML->new();
my $xslt = XML::LibXSLT->new();
my $source = XML::LibXML->load_xml(location => "$xmlFile");
my $style_doc = $parser->parse_file(Path::Class::File->new("$xsltFile"));
my $stylesheet = $xslt->parse_stylesheet($style_doc);
my $results = $stylesheet->transform($source);
$stylesheet->output_file($results, $outputFile);

Why is the <?xml> declaration unwanted? It is valid XML and and has no influence on parsers.

Related

SED command to replace test with newline break

I'm in need of some help, I need to replace text on a page using SED command with other text but it just will not work for me.
Need to replace this one:
<key>disableMailRecentsSyncing</key>
<true />
<key>allowMailDrop</key>
<false />
<key>PreventMove</key>
<true />
With this one:
<key>disableMailRecentsSyncing</key>
<false />
<key>allowMailDrop</key>
<true />
<key>PreventMove</key>
<false />
I've tried the following but it will not work:
sed -i 's/<key>disableMailRecentsSyncing</key> <true /> <key>allowMailDrop</key> <false /> <key>PreventMove</key> <true />/<key>disableMailRecentsSyncing</key> <false /> <key>allowMailDrop</key> <true /> <key>PreventMove</key> <false />/g' input.txt
For editing XML, use an XML-aware tool. For example, negating all the listed keys can be done in XSH (a wrapper around XML::LibXML) using the following command:
rename xsh:if(self::true, "false", "true")
(//false[preceding-sibling::key[1]="allowMailDrop"]
| //true[preceding-sibling::key[1]="PreventMove"
or preceding-sibling::key[1]="disableMailRecentsSyncing"]) ;
Note that I'm the current maintainer of the tool.
Assumptions:
data is nicely formatted as in the question (otherwise a proper XML/HTML-aware tool may be easier to use than re-inventing a parser)
objective is to toggle the current value (ie, true becomes false and false becomes true)
true/false values are all lowercase
true/false are always preceded by a <
With a view towards an awk solution I'd want the patterns (to search for) placed into a file as this allows for flexibility without having to hardcode the solution, eg:
$ cat key.list
disableMailRecentsSyncing
allowMailDrop
PreventMove
My input file with some additional data:
$ cat input.data
<key>disableMailRecentsSyncing</key> # match but ...
<sometimes true /> # leave "true" alone
<key>disableMailRecentsSyncing</key> # match so ...
<true /> # switch to "false"
<key>allowMailDrop</key> # match so ...
<false /> # switch to "true"
<key>PreventMove</key> # match so ...
<true /> # switch to "false"
<key>allowMailDrop</key> # match but ...
<Tuesday /> # ignore
One awk idea:
awk '
FNR==NR { keys[$1]; next }
{ split($0,arr,"[<>]") }
toggle { if ( arr[2] ~ /^true/ ) gsub(/<true/, "<false")
if ( arr[2] ~ /^false/ ) gsub(/<false/,"<true" )
toggle=0
}
{ if ( arr[3] in keys) toggle=1 }
1
' key.list input.data
This generates:
<key>disableMailRecentsSyncing</key>
<sometimes true />
<key>disableMailRecentsSyncing</key>
<false />
<key>allowMailDrop</key>
<true />
<key>PreventMove</key>
<false />
<key>allowMailDrop</key>
<Tuesday />
Due to the use of 2 input files OP will not be able to use (GNU) awk -i inplace so the output will need to be saved to a temp file and then copying/moving the temp file to replace the current file.
This might work for you (GNU sed):
cat <<\! >matchFile
<key>disableMailRecentsSyncing</key>
<true />
<key>allowMailDrop</key>
<false />
<key>PreventMove</key>
<true />
!
cat <<\! >replaceFile
<key>disableMailRecentsSyncing</key>
<false />
<key>allowMailDrop</key>
<true />
<key>PreventMove</key>
<false />
!
cat file <(echo MATCH) matchFile <(echo REPLACE) replaceFile |
sed -Ez ':a;s/(.*)(.*MATCH\1REPLACE(.*))/\3\2/;ta;s/(.*)MATCH.*//'
To replace the original file, use:
sed -E -i 'H;1h;$!d;x;s/$/\n$(echo MATCH;cat matchFile;echo REPLACE;cat replaceFile)/
s/.*/echo "&"/e;:a;s/(.*)(.*MATCH\1REPLACE(.*))/\3\2/;ta;s/(.*)MATCH.*/\1/' file
The solution appends a delimiter MATCH followed by the matchFile, followed by a second delimiter REPLACE followed by the replaceFile.
Then using a loop and pattern matching (involving back references) the matchFile is matched against places in the original file and replaced by the text in the replaceFile.

TVML: adding new lines to a description text

Experimenting with Apple TV's TVML: I'm using a Product Template, and in the description field I'd like to add carriage returns, to make it look somewhat like a list.
Here is a simple example:
var Template = function() { return `<?xml version="1.0" encoding="UTF-8" ?>
<document>
<productTemplate>
<banner>
<infoList>
</infoList>
<stack>
<title>Big Title</title>
<description>
Line one
Line two
</description>
</stack>
</banner>
</productTemplate>
</document>`
}
I've tried \n, &#xD, &#xA between the lines, and even something like this:
<![CDATA[
Line 1 <br />
Line 2 <br />
]]>
But none of these work. Is there a way to incorporate line breaks in TVML descriptions?
Having this code in a template.xml.js and loading it via the Presenter.js in the TVMLCatalog example from apple:
<stack>
<description>Insert your \n username (tipically your ID)</description>
</stack>
It renders
This also works:
var Template = function() {
const description = `
Line 1
Line 2
`.trim();
return `<?xml version="1.0" encoding="UTF-8" ?>
<document>
<productTemplate>
<banner>
<infoList>
</infoList>
<stack>
<title>Big Title</title>
<description>
${description}
</description>
</stack>
</banner>
</productTemplate>
</document>`
}

Add attribute to xml with XML::Simple

I am trying to add an attribute to existing XML, using XML::Simple.
<arbre>
<branche name="courbe" >
<description>
<![CDATA[une belle branche]]>
</description>
<feuilles>
<fleur color="blue" order="1" />
<fleur color="white" order="2" />
<fleur color="yellow" order="3" />
</feuilles>
</branche>
<branche name="droite" >
<description>
<![CDATA[une branche commune]]>
</description>
<feuilles>
<fleur color="purple" order="1" />
<fleur color="green" order="2" />
</feuilles>
</branche>
</arbre>
That I am trying to transform into :
<arbre>
<branche name="courbe" type="conifere">
<description>
<![CDATA[une belle branche]]>
</description>
<feuilles>
<fleur color="blue" order="1" />
<fleur color="white" order="2" />
<fleur color="yellow" order="3" />
</feuilles>
</branche>
<branche name="droite" type="resineux">
<description>
<![CDATA[une branche commune]]>
</description>
<feuilles>
<fleur color="purple" order="1" />
<fleur color="green" order="2" />
</feuilles>
</branche>
</arbre>
Notice the type attribute in branche tag.
So far I have the following :
#!/usr/bin/env perl -w
use strict;
use XML::Simple;
use Data::Dumper;
my $funclist = XML::Simple->new();
my $arbres = $funclist->XMLin("test.xml");
print Dumper($arbres);
exit 0;
From what I understand from the documentation $arbres is a hash in which I have to insert in each branche key the type attribute key and value.
Exept that I have no clue at where and how ($arbres{something} = "conifere" ?).
Thanks
use strict;
use warnings FATAL => 'all';
use XML::Simple qw();
my %branche_map = (
courbe => 'conifere',
droite => 'resineux',
);
my $xs = XML::Simple->new(StrictMode => 1, ForceArray => 1, KeyAttr => undef, RootName => 'arbre');
my $arbres = $xs->XMLin('test.xml');
for my $branche (#{ $arbres->{branche} }) {
$branche->{type} = $branche_map{ $branche->{name} };
}
print $xs->XMLout($arbres)
Using XML::XSH2, a wrapper around XML::LibXML
open test.xml ;
for //branche[#name='courbe'] set #type 'conifere' ;
for //branche[#name='droite'] set #type 'resineux' ;
save :b ;

Can't fetch mail body using gmail contextual gadget

Here is my manifest.xml file
<?xml version="1.0" encoding="UTF-8" ?>
<ApplicationManifest xmlns="http://schemas.google.com/ApplicationManifest/2009">
 
  <Name>My test gadget</Name>
  <Description>Test Gmail contextual gadgets for mail body</Description>
 
 
<Extension id="MailBodyReaderGadget" type="contextExtractor">
  <Name>Mail Body Reader Gadget</Name>
  <Url>google.com:EmailBodyExtractor</Url>
<Param name="body" value=".*" /> 
  <Triggers ref="mailBodyTextWidget" /> 
  <Scope ref="emailBody" />
  <Container name="mail" />
</Extension>
 
<!-- our GADGET -->
<Extension id="mailBodyTextWidget" type="gadget">
  <Name>Get mail body</Name>
  <Url>http://test.com/spec.xml</Url>
  <Container name="mail" />
</Extension>
 
<!-- gadget Scope -->
<Scope id="emailBody">
  <Url>tag:google.com,2010:auth/contextual/extractor/BODY</Url>
  <Reason>This app will show the mail body text when you click the button "Show Mail Body"</Reason>
</Scope>
</ApplicationManifest>
and spec.xml file
<?xml version="1.0" encoding="UTF-8"?>
<Module>
<ModulePrefs
height="200"
author=""
author_email=""
author_location="US">
        <Require feature="dynamic-height"/>
        <Require feature="google.contentmatch">
            <Param name="extractors">
                google.com:EmailBodyExtractor
            </Param>
        </Require>
    </ModulePrefs>
    <Content type="html" view="card">
<![CDATA[      
<script type="text/javascript">
document.write([
"\<script src='",
("https:" == document.location.protocol) ? "https://" : "http://",
"ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js' type='text/javascript'>\<\/script>"
].join(''));
</script>
<button id="btn">Show Mail Body</button>
<div id="widget" style="heigth:300px;width:500px;">
</div>
    <script>
        matches = google.contentmatch.getContentMatches();
        for (var match in matches) {
          for (var key in matches[match]) {                     
            $("#widget").html(matches[match][key]);           
          }
        }       
    </script>    
    ]]>
  </Content>
</Module>
This is my code, i have been tried to fetch mail subject and from and to email addresses it has been worked. But the main issue is i can't fetch mail body. Is there any solution to fix this?
The answer is to delete the node <Param name="body" value=".*" />
This one had me stuck, as I thought that the <param> node was required to define the name of the output parameter to use in our gadget.
But in fact the MailBodyReaderGadget outputs the parameter of "body" automatically.
So the <Param> node is only used if you wish to filter the output.
As you always want to output the body, you can delete this node entirely.
The reason it is not working at the moment is because the .* filter doesn't match return characters (which will be in the body)

Restart footnote numbering in each 'sect1' in Docbook

As the title describes my problem is that I want to reset the footnote numbering in each 'sect1', not just in each 'chapter' or 'appendix' and etc.
So far I couldn't manage to reset it. I've tried to copy a part of the footnote.xsl to my customization layer and change it but it didn't have any effect.
<xsl:template match="footnote" mode="footnote.number">
<xsl:choose>
<xsl:when test="string-length(#label) != 0">
<xsl:value-of select="#label"/>
</xsl:when>
<xsl:when test="ancestor::table or ancestor::informaltable">
<xsl:variable name="tfnum">
<xsl:number level="any" from="table|informaltable" format="1"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="string-length($table.footnote.number.symbols) >= $tfnum">
<xsl:value-of select="substring($table.footnote.number.symbols, $tfnum, 1)"/>
</xsl:when>
<xsl:otherwise>
<xsl:number level="any" from="table|informaltable"
format="{$table.footnote.number.format}"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="fnum">
<!-- * Determine the footnote number to display for this footnote, -->
<!-- * by counting all foonotes, ulinks, and any elements that have -->
<!-- * an xlink:href attribute that meets the following criteria: -->
<!-- * -->
<!-- * - the content of the element is not a URI that is the same -->
<!-- * URI as the value of the href attribute -->
<!-- * - the href attribute is not an internal ID reference (does -->
<!-- * not start with a hash sign) -->
<!-- * - the href is not part of an olink reference (the element -->
<!-- * - does not have an xlink:role attribute that indicates it is -->
<!-- * an olink, and the hrf does not contain a hash sign) -->
<!-- * - the element either has no xlink:type attribute or has -->
<!-- * an xlink:type attribute whose value is 'simple' -->
<!-- * -->
<!-- * Note that hyperlinks are counted only if both the value of -->
<!-- * ulink.footnotes is non-zero and the value of ulink.show is -->
<!-- * non-zero -->
<!-- FIXME: list in #from is probably not complete -->
<xsl:number level="any"
from="chapter|appendix|preface|article|refentry|bibliography|sect1"
count="footnote[not(#label)][not(ancestor::table) and not(ancestor::informaltable)]
|ulink[$ulink.footnotes != 0][node()][#url != .][not(ancestor::footnote)][$ulink.show != 0]
|*[node()][#xlink:href][not(#xlink:href = .)][not(starts-with(#xlink:href,'#'))]
[not(contains(#xlink:href,'#') and #xlink:role = $xolink.role)]
[not(#xlink:type) or #xlink:type='simple']
[not(ancestor::footnote)][$ulink.footnotes != 0][$ulink.show != 0]
"
format="1"/>
</xsl:variable>
<xsl:choose>
<xsl:when test="string-length($footnote.number.symbols) >= $fnum">
<xsl:value-of select="substring($footnote.number.symbols, $fnum, 1)"/>
</xsl:when>
<xsl:otherwise>
<xsl:number value="$fnum" format="{$footnote.number.format}"/>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Well, I guess this is the rule for footnote numbering reset. As I tried to modify this part of 'footnote.xsl', it didn't work and the xsltproc returned with a bunch of errors.
Like this:
Undefined namespace prefix.
1 object left on the stack.