NAnt task xmllist, way more powerful than xmlpeek (source provided)

UPDATE: See http://weblogs.asp.net/soever/archive/2006/12/01/nant-xmllist-command-updated.aspx for an updated version of the NAnt XmlPeek command. 

I have a love-hate relationship with the <xmlpeek> command in NAnt.

The problems I have with it are:

  • It report an error when the XPath expression does not resolve into a node, there is NO WAY to test if a node or attribute exists (to my knowledge)
  • It’s logging level is set to Level.Info, so there is always output. This should have been Level.Verbose, I don’t want output for every xmlpeek I perform
  • It is not possible to return the contents of multiple nodes selected in the XPath expression

Especially the problem that I can’t test for the existance of a node or attribute bothers me. I can set failonerror to false, ant test afterwards if the property exist, but that means that there is still an error that is reported in my buildserver report, while it is expected behaviour!

Based on an implementation by Richard Case I wrote the same version of his <xmllist> task, but a bit more powerful and using the standard naming for the attributes. Using this task you can extract text from an XML file at the locations specified by an XPath expression, and return those texts separated by a delimiter string. If the XPath expression specifies multiple nodes the node are seperated by the delimiter string, if no nodes are matched, an empty string is returned.

See the comments in the code for an extensive example.

I will try to post this code to the NAnt developers mailing list, but it’s here for you to get you starget if you need this kind of functionality.

// NAnt - A .NET build tool
// Copyright (C) 2001-2003 Gerry Shaw
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
// Serge van den Oever (serge@macaw.nl)
// Based on idea from weblog entry: http://blogs.geekdojo.net/rcase/archive/2005/01/06/5971.aspx combined with the code of xmlpeek.

using System; using System.Globalization; using System.IO; using System.Text; using System.Xml; using System.Collections.Specialized;

using NAnt.Core; using NAnt.Core.Attributes; using NAnt.Core.Types;

namespace Macaw.MGDE { /// <summary> /// Extracts text from an XML file at the locations specified by an XPath /// expression, and return those texts separated by a delimiter string. /// </summary> /// <remarks> /// <para> /// If the XPath expression specifies multiple nodes the node are seperated /// by the delimiter string, if no nodes are matched, an empty string is returned. /// </para> /// </remarks> /// <example> /// <para> /// The example provided assumes that the following XML file (xmllisttest.xml) /// exists in the current build directory. /// </para> /// <code> /// <![CDATA[ /// <?xml version="1.0" encoding="utf-8" ?> /// <xmllisttest> /// <firstnode attrib="attrib1">node1</firstnode> /// <secondnode attrib="attrib2"> /// <subnode attrib="attribone">one</subnode> /// <subnode attrib="attribtwo">two</subnode> /// <subnode attrib="attribthree">three</subnode> /// <subnode attrib="attribtwo">two</subnode> /// </secondnode> /// </xmllisttest> /// ]]> /// </code> /// </example> /// <example> /// <para> /// The example reads numerous values from this file: /// </para> /// <code> /// <![CDATA[ /// <?xml version="1.0" encoding="utf-8" ?> /// <project name="tests.build" default="test" basedir="."> /// <target name="test"> /// <!-- TEST1: node exists, is single node, get value --> /// <xmllist file="xmllisttest.xml" property="prop1" delim="," xpath="/xmllisttest/firstnode"/>
/// <echo message="prop1=${prop1}"/> /// <fail message="TEST1: Expected: prop1=node1" unless="${prop1 == 'node1'}"/> /// /// <!-- TEST2: node does not exist --> /// <xmllist file="xmllisttest.xml" property="prop2" delim="," xpath="/xmllisttest/nonexistantnode" />
/// <echo message="prop2='${prop2}'"/> /// <fail message="TEST2: Expected: prop2=<empty>" unless="${prop2 == ''}"/> /// /// <!-- TEST3: node exists, get attribute value --> /// <xmllist file="xmllisttest.xml" property="prop3" delim="," xpath="/xmllisttest/firstnode/@attrib" />
/// <echo message="prop3=${prop3}"/> /// <fail message="TEST3: Expected: prop3=attrib1" unless="${prop3 == 'attrib1'}"/> /// /// <!-- TEST4: nodes exists, get multiple values --> /// <xmllist file="xmllisttest.xml" property="prop5" delim="," xpath="/xmllisttest/secondnode/subnode" />
/// <echo message="prop5=${prop5}"/> /// <fail message="TEST4: Expected: prop5=one,two,three,two" unless="${prop5 == 'one,two,three,two'}"/> /// /// <!-- TEST5: nodes exists, get multiple attribute values --> /// <xmllist file="xmllisttest.xml" property="prop5" delim="," xpath="/xmllisttest/secondnode/subnode/@attrib" />
/// <echo message="prop5=${prop5}"/> /// <fail message="TEST5: Expected: prop5=attribone,attribtwo,attribthree,attribtwo" unless="${prop5 == 'attribone,attribtwo,attribthree,attribtwo'}"/> /// /// <!-- TEST6: nodes exists, get multiple values, but only unique values --> /// <xmllist file="xmllisttest.xml" property="prop6" delim="," xpath="/xmllisttest/secondnode/subnode" unique="true"/>
/// <echo message="prop6=${prop6}"/> /// <fail message="TEST4: Expected: prop6=one,two,three" unless="${prop6 == 'one,two,three'}"/> /// /// <!-- TEST7: nodes exists, get multiple attribute values --> /// <xmllist file="xmllisttest.xml" property="prop7" delim="," xpath="/xmllisttest/secondnode/subnode/@attrib" unique="true"/>
/// <echo message="prop7=${prop7}"/> /// <fail message="TEST7: Expected: prop7=attribone,attribtwo,attribthree" unless="${prop7 == 'attribone,attribtwo,attribthree'}"/> /// /// <!-- TEST8: node exists, is single node, has namespace http://thirdnodenamespace, get value --> /// <xmllist file="xmllisttest.xml" property="prop8" delim="," xpath="/xmllisttest/x:thirdnode">
/// <namespaces> /// <namespace prefix="x" uri="http://thirdnodenamespace" /> /// </namespaces> /// </xmllist> /// <echo message="prop8=${prop8}"/> /// <fail message="TEST8: Expected: prop8=namespacednode" unless="${prop8 == 'namespacednode'}"/> /// </target> /// </project> /// ]]> /// </code> /// Result when you run this code: /// <code> /// <![CDATA[ /// test: /// /// [echo] prop1="node1" /// [echo] prop2="''" /// [echo] prop3="attrib1" /// [echo] prop5="one,two,three,two" /// [echo] prop5="attribone,attribtwo,attribthree,attribtwo" /// [echo] prop6="one,two,three" /// [echo] prop7="attribone,attribtwo,attribthree" /// [echo] prop8="namespacednode" /// /// BUILD SUCCEEDED /// ]] /// </code> /// </example> [TaskName ("xmllist")] public class XmlListTask : Task { #region Private Instance Fields

	private FileInfo _xmlFile;
	private string _xPath;
	private string _property;
	private string _delimiter = &quot;,&quot;;
	private bool _unique = false; // assume we return all values
	private XmlNamespaceCollection _namespaces = new XmlNamespaceCollection();

	#endregion Private Instance Fields

	#region Public Instance Properties
	/// &lt;summary&gt;
	/// The name of the file that contains the XML document
	/// that is going to be interrogated.
	/// &lt;/summary&gt;
	[TaskAttribute(&quot;file&quot;, Required=true)]
	public FileInfo XmlFile 
	{
		get
		{
			return _xmlFile;
		}
		set
		{
			_xmlFile = value;
		}
	}

	/// &lt;summary&gt;
	/// The XPath expression used to select which nodes to read.
	/// &lt;/summary&gt;
	[TaskAttribute (&quot;xpath&quot;, Required = true)]
	[StringValidator (AllowEmpty = false)]
	public string XPath
	{
		get
		{
			return _xPath;
		}
		set
		{
			_xPath = value;
		}
	}

	/// &lt;summary&gt;
	/// The property that receives the text representation of the XML inside 
	/// the nodes returned from the XPath expression, seperated by the specified delimiter.
	/// &lt;/summary&gt;
	[TaskAttribute (&quot;property&quot;, Required = true)]
	[StringValidator (AllowEmpty = false)]
	public string Property
	{
		get
		{
			return _property;
		}
		set
		{
			_property = value;
		}
	}

	/// &lt;summary&gt;
	/// The delimiter string.
	/// &lt;/summary&gt;
	[TaskAttribute (&quot;delim&quot;, Required = false)]
	[StringValidator (AllowEmpty = false)]
	public string Delimiter
	{
		get
		{
			return _delimiter;
		}
		set
		{
			_delimiter = value;
		}
	}

	/// &lt;summary&gt;
	/// If unique, no duplicate vaslues are returned. By default unique is false and all values are returned.
	/// &lt;/summary&gt;
	[TaskAttribute (&quot;unique&quot;, Required = false)]
	[BooleanValidator()]
	public bool Unique
	{
		get
		{
			return _unique;
		}
		set
		{
			_unique = value;
		}
	}

	/// &lt;summary&gt;
	/// Namespace definitions to resolve prefixes in the XPath expression.
	/// &lt;/summary&gt;
	[BuildElementCollection(&quot;namespaces&quot;, &quot;namespace&quot;)]
	public XmlNamespaceCollection Namespaces 
	{
		get
		{
			return _namespaces;
		}
		set
		{
			_namespaces = value;
		}
	}

	#endregion Public Instance Properties

	#region Override implementation of Task

	/// &lt;summary&gt;
	/// Executes the XML reading task.
	/// &lt;/summary&gt;
	protected override void ExecuteTask() 
	{
		Log(Level.Verbose, &quot;Looking at &#39;{0}&#39; with XPath expression &#39;{1}&#39;.&quot;, 
			XmlFile.FullName,  XPath);

		// ensure the specified xml file exists
		if (!XmlFile.Exists) 
		{
			throw new BuildException(string.Format(CultureInfo.InvariantCulture, 
				&quot;The XML file &#39;{0}&#39; does not exist.&quot;, XmlFile.FullName), Location);
		}
		try 
		{
			XmlDocument document = LoadDocument(XmlFile.FullName);
			Properties[Property] = GetNodeContents(XPath, document);
		} 
		catch (BuildException ex) 
		{
			throw ex; // Just re-throw the build exceptions.
		} 
		catch (Exception ex) 
		{
			throw new BuildException(string.Format(CultureInfo.InvariantCulture,
				&quot;Retrieving the information from &#39;{0}&#39; failed.&quot;, XmlFile.FullName), 
				Location, ex);
		}
	}
    
	#endregion Override implementation of Task
    
	#region private Instance Methods

	/// &lt;summary&gt;
	/// Loads an XML document from a file on disk.
	/// &lt;/summary&gt;
	/// &lt;param name=&quot;fileName&quot;&gt;The file name of the file to load the XML document from.&lt;/param&gt;
	/// &lt;returns&gt;
	/// A &lt;see cref=&quot;XmlDocument&quot;&gt;document&lt;/see&gt; containing
	/// the document object representing the file.
	/// &lt;/returns&gt;
	private XmlDocument LoadDocument(string fileName)  
	{
		XmlDocument document = null;

		try 
		{
			document = new XmlDocument();
			document.Load(fileName);
			return document;
		} 
		catch (Exception ex) 
		{
			throw new BuildException(string.Format(CultureInfo.InvariantCulture,
				&quot;Can&#39;t load XML file &#39;{0}&#39;.&quot;, fileName), Location, 
				ex);
		}
	}

	/// &lt;summary&gt;
	/// Gets the contents of the list of nodes specified by the XPath expression.
	/// &lt;/summary&gt;
	/// &lt;param name=&quot;xpath&quot;&gt;The XPath expression used to determine the nodes.&lt;/param&gt;
	/// &lt;param name=&quot;document&quot;&gt;The XML document to select the nodes from.&lt;/param&gt;
	/// &lt;returns&gt;
	/// The contents of the nodes specified by the XPath expression, delimited by 
	/// the delimiter string.
	/// &lt;/returns&gt;
	private string GetNodeContents(string xpath, XmlDocument document) 
	{
		XmlNodeList nodes;

		try 
		{
			XmlNamespaceManager nsMgr = new XmlNamespaceManager(document.NameTable);
			foreach (XmlNamespace xmlNamespace in Namespaces) 
			{
				if (xmlNamespace.IfDefined &amp;&amp; !xmlNamespace.UnlessDefined) 
				{
					nsMgr.AddNamespace(xmlNamespace.Prefix, xmlNamespace.Uri);
				}
			}
			nodes = document.SelectNodes(xpath, nsMgr);
		} 
		catch (Exception ex) 
		{
			throw new BuildException(string.Format(CultureInfo.InvariantCulture,
				&quot;Failed to execute the xpath expression {0}.&quot;, xpath), 
				Location, ex);
		}

		Log(Level.Verbose, &quot;Found &#39;{0}&#39; nodes with the XPath expression &#39;{1}&#39;.&quot;,
			nodes.Count, xpath);

		// collect all strings in a string collection, skip duplications if Unique is true
		StringCollection texts = new StringCollection();
		foreach (XmlNode node in nodes)
		{
			string text = node.InnerText;
			if (!Unique || !texts.Contains(text))
			{
				texts.Add(text);
			}
		}
		
		// Concatenate the strings in the string collection to a single string, delimited by Delimiter
		StringBuilder builder = new StringBuilder();
		foreach (string text in texts)
		{
			if (builder.Length &gt; 0)
			{
				builder.Append(Delimiter);
			}
			builder.Append(text);
		}

		return builder.ToString();
	}
	#endregion private Instance Methods
}

}

26 Comments

  • Hello Serge,

    Would you mind if I added this to the NAntContrib project?

    I've found it very useful!

    Thanks,
    Matt

  • Hi Matt, would be great if you could get it included.

  • Hi Serge,
    The xmllist task is great. I just sent you an email with an updated version of the xmllist task.

    I have added the ability to expand properties within the xml file.

    eg for the file

    ${myproperty}


    can use:





    I also fixed errors in the other examples.

    The file still needs to be converted to the correct nant formatting.


  • интеретсно написано

  • Interesting theme, I will take part. Together we can come to a right answer. I am assured.

  • lucky to have used entire webpages and look forward to plenty of more fabulous times
    reading here. Thanks again for everything.

  • Respect to article author , some fantastic information .

  • know find out how to convey an issue to gentle and make

  • I'd perpetually want to be update on new blog on this web site , saved to fav! .

  • I relish, cause I found exactly what I was taking a look for.

    You've ended my four day lengthy hunt! God Bless you man. Have a nice day. Bye

  • Hello, I want to subscribe for this web site to get newest updates, so where can i do it please help out.

  • Krajowy Rejestr Sądowniczy wydaje odpis całkowity, który opatrzony jest pieczęciami i zwiera aktulane informacje na temat przedsiębiorców.

    Krajowy Rejestr Sądowniczy w Wrocławiu uruchomił opcję otrzymania pełnego odpisu przez internet.
    Taka możliwość to olbrzymi skok w kierunku ułatwienia życia społecznego.
    Tego typu rozwiązania, dają możliwość sprawnego wygodnego
    zebrania danych o naszym kliencie. Dzięki temu mamy możliwość ustrzec się przed naciągactwu i
    zawarciu niekorzystnych transakcji.

  • KRS wydaje odpis pełny, który opatrzony jest pieczęciami i zwiera aktulane
    informacje na temat przedsiębiorców.
    KRS w Poznaniu włączył możliwość otrzymania cyklicznego odpisu przez internet.
    Taka opcja to duży skok w stronę cyfryzacji administracji i sądownictwa.
    Tego typu rozwiązania, pozwalają sprawnego wygodnego uzyskania danych o
    naszym kliencie. Dzięki temu mamy możliwość ustrzec się przed naciągactwu i zawarciu niepotrzebnych
    transakcji.

  • Panele ogrodzeniowe są wyjątkowo skutecznym rozwiązaniem gdy chodzi
    o zakup techniki grodzeń, jest skuteczny zarówno na terenie prywatnych posesji, ale również przy najzwyklejszym ogrodzeniu sadów
    oraz fabryk. Montażsiatki leśnej zapewnia zabezpieczenie
    przez amatorami cudzej własności. Główną cechą chzakterystyczną jest duża funkcjonalność.
    Istotą takiego rozwiąznia, jest też jego trwałość oraz .
    Polecam.

  • KRS wydaje odpis pełny, który opatrzony jest pieczęciami i zwiera aktulane informacje na temat przedsiębiorców.

    KRS w Toruniu dodał alternatywę otrzymania cyklicznego odpisu za
    pośrednictwem ostrony www. Takie rozwiązanie to olbrzymi krok w kierunku ułatwienia życia społecznego.

    Tego typu możliwości, pozwalają sprawnego wygodnego
    otrzymania danych o naszym kliencie. Dzięki temu
    możemy zapobiec naciągactwu i zawarciu niekorzystnych transakcji.

  • Pierwsze kroki naszej pociechy w przedszkolu, powinien zapmiętać
    jako coś wspaniałego i nie wywołuje u niego obawy.
    Jeśli natknie się na coś niemiłego i niedobrego, może to zachwiać jego samoocene.

    Po takim przeżyciu pierwszego dnia, spotkania ze szkołą, dziecko, będzie odczuwać obawę przed pojściem do
    szkoły następnego dnia. Aby uchronić przed tym swoje dziecko, powinno się zdecydować szkołę publiczną.

    Znajdziemy w takim miejscu dużo więcej opieki, która potrzebna jest dla dziecka,
    na tym etapie jego życia. Do licznych plusów
    co do prywatnych placówek należy zajiczyć też min.
    zajęcia wprowadzające i powalające na asymilacje nowego członka przedszkola oraz ciekawe zajęcia obowiązkowe, np.
    rytmika, angielski, zajęcia taneczne. W przedszkolu
    bardzo wskazana jest włąściwa atmosfera która,
    da dziecku poczucie bezpieczeństwa. W publicznych przedszkolach
    młody człowiek może liczyć na indywidualne podejście oraz radę a to
    pomoże nawiązać kontakt z z innymi podopiecznymi placówki i zawiązać pierwsze kontkty.
    Niepubliczne ośrodki, szkolą swoje grupy do pierwszego roku szkolnego.

  • Jeśli macie ochotę to zalecam używać kabiny prysznicowe.
    Jest to bardzo dobra alternatywa, która pozwala zrobić z łazienki wspólne miejsce,
    gdzie można ukoić skołatane nerwy oraz przede wsystkim nie zużywając dużo wody wziąć
    kąpiel. Wymontowanie wanny na rzecz natrysku prysznicowego niesie ze sobą zauważalne różnice w rachunkach za wode
    i ogrzewanie. Używając kabiny prysznicowej zużywamy znacznie mniej wody i prądu.

    Po za tym, prysznic jest o wiele łatwiejszy w używaniu najbardziej dla ludzi starszych.

  • Jeśli chcecie to polecam użytkować natrysk prysznicowy.
    Jest to bardzo ciekawa zastępstwo, która pozwala sprawić
    z łazienki Twoje miejsce, gdzie można uspokoić się oraz co
    najwżniejsze sybko wziąć przysznic. Wymontowanie wanny na rzecz natrysku prysznicowego daje nam zauważalne różnice w rachunkach za wode
    i ogrzewanie. Używając natrysku prysznicowego zużywamy dużo mniej wody i prądu.
    Oprócz tego, kabina prysznicowa jest o wiele łatwiejszy w eksploatowaniu przede wszystkim ludzi
    , którzy mają już swoje lata.

  • Zaręczyny już wkrótce więc nadszedł czas aby
    odszukać ten cudowny i zjawiskowy pierścionek zaręczynowy.
    W dzisiejszych czasach wybór pierścionka zaręczynowego
    zależy od indywidualnego gustu przyszłego narzeczonego oraz od szczodrości.

    Oferta sklepów jubilerskich jest bardzo szeroka. Jaki pierścionek zaręczynowy
    wybrać? Zwyczaj mówi o złotym, to nie budzi u nikogo żadnych wątpliwości.
    Jeżeli przyszła narzeczona nie lubi żółtej biżuterii można zdecydować się na srebro,
    które równie cudownie i szykownie wygląda. Biżuteria może też
    być wykonana z dwóch kruszców np. platyny i złota lub żółtego złota i srebra co w połączeniu z cyrkoniami i diamentami olśni
    naszą wybrankę. Fajnym pomysłem na poszukiwanie pierścionka jest sklep online.
    Takie wyjście z sytuacji jest pożyteczne, bo mamy możliwość komfortowego obejrzenia i porównania biżuterii.

  • Pierwszy dzień dziecka w przedszkolu, musi zapmiętać jako coś wesołego i
    nie wywołuje u niego strachu. Jeśli spotka go coś złego i
    obraźliwego, może to naruszyć jego pewność siebie.
    Po takim przeżyciu pierwszego dnia, spotkania ze szkołą, dziecko, może
    odczuwać zdenerowowanie przed pojściem do przedszkola w przyszłości.
    Aby temu zapobiec, powinno się zdecydować szkołę publiczną.

    Znajdziemy w takim miejscu dużo więcej opieki, która konieczna jest dla naszej pociechy, na tym etapie jego życia.
    Do licznych plusów co do praywatnych przedszkoli należy zajiczyć też min.
    zajęcia wprowadzające i powalające na asymilacje nowego członka przedszkola oraz ciekawe zajęcia obowiązkowe, np.
    rytmika, angielski, zajęcia taneczne. W przedszkolu musi włąściwa
    atmosfera która, da dziecku poczucie bezpieczeństwa.
    W publicznych przedszkolach dziecko może liczyć na indywidualne podejście oraz opiekę a to
    pomoże nawiązać kontakt z z innymi podopiecznymi
    placówki i zawiązać pierwsze przyjaźnie. Prywatne placówki, szkolą
    swoje grupy do pierwszego roku szkolnego.

  • Oświadczyny już niebawem więc nastąpił okres aby odszukać ten niepowtarzalny i prześliczny pierścionek zaręczynowy.
    W dzisiejszych czasach wybór pierścionka zaręczynowego zależy od preferencji narzeczonej przyszłego narzeczonego oraz od kasy.
    Propozycje punktów jubilerskich jest bardzo bogata. Jaki pierścionek zaręczynowy wybrać?
    Tradycja mówi o złotym, każdy o tym wie. Jeżeli przyszła oblubienica nie
    lubi żółtej biżuterii można zdecydować się na platynę, które równie pięknie i
    adekwatnie wygląda. Biżuteria może też być wykonana z dwóch kruszców
    np. białego i żółtego złota lub żółtego złota i srebra co w połączeniu z rubinem i szafirami
    olśni naszą przyszłą żonę. Fajnym rozwiązaniem na poszukiwanie pierścionka
    jest sklep online. Takie rozwiązanie jest pragmatyczne, ponieważ możemy spokojnego obejrzenia i porównania
    biżuterii.

  • Przęsła ogrodzeniowe są najlepszym rozwiązaniem gdy chodzi o montarz odpowiedniego dla Nas sposobu grodzeń, jest bardzo wydajny zarówno
    na terenie prywatnych działek, ale również przy najzwyklejszym ogrodzeniu sadów oraz lasów.
    Zdecydowanie się napaneli ogrodzeniowych daje zabezpieczenie przez amatorami cudzej
    własności. Główną cechą chzakterystyczną jest mnogość zastosowań.
    Zaletą takiego rozwiąznia, jest też jego skuteczność oraz prosty montarz.
    Polecam.

  • Tydzień temu wyszukałem genialny sposób
    na problemy z Allegro.pl. Bardzo często używam z tej strony, a dokładniej
    cały czas ponieważ prowadzę sklep online.
    W ogromie propozycji kupna/sprzedaży na allegro szybko można zagubić
    się i być kompletnie niezauważalnym dla pozostałych użytkowników.
    Oczywiście prowadzi to do zmniejszenia naszych wpływów i ilości przeprowadzonych transakcji.
    Aby temu zapobiec należy używać panelu aukcji allegro, za pomocą którego
    ktoś kto trafi na naszą pozycje będzie automatycznie przekierowywany na inne nasze
    aukcje. Takie zamknięte koło będzie zatrzymywał innego allegrowicza przy naszych aukcjach, który z biegiem czasu nam zaufa i
    nie będzie zawierał transakcji z innych użytkowników portalu.

  • Panele ogrodzeniowe są wyjątkowo skutecznym rozwiązaniem gdy chodzi o montarz systemu grodzeń,
    który sprawdza się zarówno na terenie publicznych terenów,
    jak i przy najzwyklejszym ogrodzeniu pól uprawnych oraz całych gospodarstw.
    Montażsiatki leśnej zapewnia straż przez amatorami cudzej własności.
    Główną cechą chzakterystyczną jest duża funkcjonalność.

    Najbardziej wartościową storną takiego rozwiąznia, jest też jego skuteczność
    oraz łatwy montarz. Polecam.

  • Well done on your latest blog entry. I have been looking for more data on the subject for along time now.

Comments have been disabled for this content.