Reorder XML Elements in Extendscript Indesign - adobe-indesign

I want to reorder the following list of movies in such a way that movies of the version==3D should be placed before the ones in version==2D.
Input
<films>
<film name="Foobar" version="2D"></film>
<film name="Foobar" version="3D"></film>
<film name="Foobaz" version="2D"></film>
<film name="Foobaz" version="3D"></film>
</films>
Desired Output
<films>
<film name="Foobar" version="3D"></film>
<film name="Foobar" version="2D"></film>
<film name="Foobaz" version="3D"></film>
<film name="Foobaz" version="2D"></film>
</films>
I've fiddled around and ended up with the following code. Hopefully it understandable.
/***
Extend Array prototype to have a indeOf function
***/
Array.prototype.indexOf = function(item) {
var index = 0, length = this.length;
for ( ; index < length; index++ ) {
if ( this[index] == item )
return index;
}
return -1;
};
var xmlString = '\
<films>\
<film name="Foobar" version="2D"></film>\
<film name="Foobar" version="3D"></film>\
<film name="Foobaz" version="2D"></film>\
<film name="Foobaz" version="3D"></film>\
</films>';
var xml = new XML(xmlString);
var sortVersion = ['2D', '3D'];
var uniqueMovies = new Array();
for (var index = 0; index < xml.elements().length(); index++) {
if (index == 0) continue;
// Fetch meta data of previous movie
var prevTitle = xml.elements()[index - 1].#name;
var prevVersion = xml.elements()[index - 1].#version;
var prevVersionIdx = sortVersion.indexOf(prevVersion);
// Fetch meta data of current movie
var curTitle = xml.elements()[index].#name;
var curVersion = xml.elements()[index].#version;
var curVersionIdx = sortVersion.indexOf(curVersion);
// If both movie title matches verify which movie should be prioritized
if (prevTitle == curTitle) {
if (prevVersionIdx < curVersionIdx) {
// Movie prio movie before less prio movie
xml.insertChildBefore(xml.elements()[index - 1], xml.elements()[index]);
// And delete the next in index
delete xml.elements()[index + 1];
}
}
}
$.writeln("-----");
$.writeln("");
$.writeln(xml.elements().toString());
return xml
However when I run this script I end with the following result where nothing is changed at all although the both if condition are hit and the element on index[1] is added before index[0].
<films>
<film name="Foobar" version="2D"/>
<film name="Foobar" version="3D"/>
<film name="Foobaz" version="2D"/>
<film name="Foobaz" version="3D"/>
</films>
Does anyone have an idea what I am doing wrong here?

Consider the following approach instead:
script.jsx
/**
* Extend Array prototype to have a indexOf function
*/
Array.prototype.indexOf = function(item) {
var index = 0, length = this.length;
for ( ; index < length; index++ ) {
if ( this[index] == item )
return index;
}
return -1;
};
var xmlString = '\
<films>\
<film name="Foobar" version="2D"></film>\
<film name="Foobar" version="3D"></film>\
<film name="Foobaz" version="2D"></film>\
<film name="Foobaz" version="3D"></film>\
<film name="Foo" version="3D"></film>\
<film name="Quux" version="2D"></film>\
<film name="Quux" version="3D"></film>\
</films>';
var xml = new XML(xmlString);
var sortVersion = ['2D', '3D'];
// 1. Create a temporary XML object with a matching `films` root element.
var tempXml = new XML('<' + xml.name() + '></' + xml.name() + '>');
for (var index = 0, max = xml.elements().length(); index < max; index++) {
// Fetch meta data of previous movie
var prevTitle = String(xml.elements()[index - 1].#name);
var prevVersion = xml.elements()[index - 1].#version;
var prevVersionIdx = sortVersion.indexOf(prevVersion);
// Fetch meta data of current movie
var curTitle = String(xml.elements()[index].#name);
var curVersion = xml.elements()[index].#version;
var curVersionIdx = sortVersion.indexOf(curVersion);
// 2. Insert each XML element node from the original XML Object into
// the temporary XML Object and position (i.e. sort) as neccessary.
if (prevTitle === curTitle && prevVersion < curVersion) {
tempXml.insertChildBefore(tempXml.elements()[index -1], xml.elements()[index])
} else {
// There's nothing to change (i.e. sort) so insert it as-is .
tempXml.insertChildBefore(tempXml.elements()[index], xml.elements()[index])
}
}
// 3. Overwrite original XML object's children with temporary (sorted) XML objects children.
xml.setChildren(tempXml.children());
// 4. Delete temporary XML object.
tempXml = undefined;
// Testing...
$.writeln(xml)
$.writeln('-----------------')
$.writeln(xml.elements().length())
$.writeln('-----------------')
Explanation:
Instead of attempting to manipulate (i.e. sort) the original XML Object the gist above does the following instead:
Creates another XML Object that contains a <films> root element only. This XML Object will be temporary.
During each turn of the for loop we;
Insert a clone of each XML element node from the original XML Object into the temporary XML Object.
Position (i.e. sort) each XML element node as necessary.
Overwrite the original XML objects child element nodes with child element nodes from the temporary/sorted XML Object.
Finally we delete the temporary XML Object (i.e. set it to undefined) as it's no longer required.
Result
The example gist above includes the following source XML:
<films>
<film name="Foobar" version="2D"></film>
<film name="Foobar" version="3D"></film>
<film name="Foobaz" version="2D"></film>
<film name="Foobaz" version="3D"></film>
<film name="Foo" version="3D"></film>
<film name="Quux" version="2D"></film>
<film name="Quux" version="3D"></film>
</films>
which will be transformed to the following:
<films>
<film name="Foobar" version="3D"/>
<film name="Foobar" version="2D"/>
<film name="Foobaz" version="3D"/>
<film name="Foobaz" version="2D"/>
<film name="Foo" version="3D"/>
<film name="Quux" version="3D"/>
<film name="Quux" version="2D"/>
</films>
Note: For more complex XML structures and transformation requirements than the example provided in your question, I'd consider utilizing XSLT instead. Given your current requirement a template like this one will achieve your desired result.

Related

Trying to understand how to conditionally write out element/attributes with LIINQToXML

This is the first time that I created a XML document using LINQToXML.
I am trying to understand how can I conditionally create attributes(or elements) when creating my document?
In this example a given car may/may not have a feature to it, so in that case I would not want to create that element, I also may have certain attributes in the feature node that could be missing. How could I handle these scenarios?
XDocument xDoc = new XDocument(
new XElement("root",
new XElement("NodeA"),
new XElement("Cars",
from p in listCars
select new XElement("Car", new XAttribute("name", p.CarName),
new XElement("Feature", new XAttribute("door", p.Door), new XAttribute("model", p.Model))
)
)
)
);
Desired result #1 (All features are missing for a given car):
<root>
<NodeA />
<Cars>
<Car name="Honda">
<Feature door="4" model="Accord" />
</Car>
<Car name="Ford" />
</Cars>
</root>
Desired result #2 (Some features could exist)
<root>
<NodeA />
<Cars>
<Car name="Honda">
<Feature door="4" model="Accord" />
</Car>
<Car name="Ford">
<Feature model="Focus" />
</Car>
</Cars>
</root>
2 seperate solutions in here. Either use a method to create the features node, or do it all in one:
static void Main(string[] args)
{
var listCars = new List<Car>();
listCars.Add(new Car { CarName = "test 1", Door = "0", Model = "" });
listCars.Add(new Car { CarName = "test 2", Door = "", Model = "" });
listCars.Add(new Car { CarName = "test 3", Door = "0", Model = "0" });
XDocument xDoc2 = new XDocument(
new XElement("root",
new XElement("NodeA"),
new XElement("Cars",
from p in listCars
select new XElement("Car",
new XAttribute("name", p.CarName),
p.Door != "" || p.Model != "" ?
new XElement("Feature",
p.Door != "" ? new XAttribute("door", p.Door) : null,
p.Model != "" ? new XAttribute("model", p.Model) : null) : null
)
)
)
);
XDocument xDoc = new XDocument(
new XElement("root",
new XElement("NodeA"),
new XElement("Cars",
from p in listCars
select new XElement("Car",
new XAttribute("name", p.CarName),
CreateFeature(p)
)
)
)
);
}
static XElement CreateFeature(Car p)
{
var el = new XElement("Feature",
p.Door != "" ? new XAttribute("door", p.Door) : null,
p.Model != "" ? new XAttribute("model", p.Model) : null);
return !el.Attributes().Any() ? null : el;
}
If you supply null instead of an element, it will be ignored, so you can use constructs like the following.
p.CarName != null ? new XAttribute("name", p.CarName) : null
If you're using C# 6, you can use null propagation.

How to find XElement specific Children (in c# with LINQ)?

I've got an XML file like this :
<root>
<environment env="PROD">
<key name="Big Key" propagate="true" value="21" />
</environment>
<environment env="PRE-PROD">
<key name="First Key" propagate="true" value="4" />
<key name="Second Key" propagate="true" value="3" />
</environment>
</root>
I want to check if a key exist in that file, and if the propagate item is true.
I manage to get those 2 System.Xml.Linq.XElement : key name="First Key" AND key name="Second Key".
but I would like to get only the one by pKeyname (like "Second Key" for eg.) I can't find how...
public static bool IsPropagate(string pXmlFileName, string pEnvironment, string pKeyname)
{
var doc = XElement.Load(pXmlFileName);
IEnumerable<XElement> childList = doc.Elements("environment")
.Where(elt => elt.Attribute("env").Value == pEnvironment)
.Elements();
if (childList.Any())
return true;
return false;
}
Any help would be highly appreciated!
This would help to get the exact key:
public static bool IsPropagate(string pXmlFileName, string pEnvironment,
string pKeyname)
{
var doc = XElement.Load(pXmlFileName);
IEnumerable<XElement> childList = doc.Elements("environment")
.Where(elt => elt.Attribute("env").Value == pEnvironment)
.Elements()
.Where(a => a.Attribute("name").Value == pKeyname);
if (childList.Any())
return true;
return false;
}
It's working by adding the "FirstOrDefault"! Thanks.
public static bool IsPropagate(string pXmlFileName, string pEnvironment, string pKeyname)
{
var doc = XElement.Load(pXmlFileName);
XElement child = doc.Elements("environment")
.Where(elt => elt.Attribute("env").Value == pEnvironment)
.Elements()
.FirstOrDefault(a => a.Attribute("name").Value == pKeyname);
if (child != null)
return true;
return false;
}

rss feed retrieval using LINQ

I have a rss feed which is below format.
<response>
<results>
<game>
<image>
<icon_url>
<![CDATA[
http://-------------------
]]>
</icon_url>
<medium_url>
<![CDATA[
http://----
]]>
</medium_url>
</image>
</game>
</results>
</response>
Now, I want to retrieve the medium_url using LINQ.
var items = from item in rssFeed.
Elements("response").Elements("results").Elements("game")
select new
{
Image1 = item.Element("image").Element("medium_url").value
}
This doesn't seem to work.
You just need to make a small change:
var items = from item in rssFeed.
Elements("response").Elements("results").Elements("game")
select new
{
Image1 = (string)item.Element("image").Element("medium_url")
};
or
var items = from item in rssFeed.
Elements("response").Elements("results").Elements("game")
select new
{
Image1 = item.Element("image").Element("medium_url").Value
};

How to display video from xml file?

Hi am using xml file given below,how can i get videos from xml file?
<Category name="Videos">
<article articleid="68">
<videourl>
<iframe src="http://player.vimeo.com/video/52375409?fullscreen=0" width="500" height="298" frameborder="0"></iframe>
</videourl>
</article>
</Category>
My Code is
XDocument loadedData = XDocument.Load("CountriesXML.xml");
var data = from query in loadedData.Descendants("Country")
select new CountryData
{
url = (string)query.Element("videourl").Elements("iframe").Single().Attribute("src").Value,
};
countryList = data.ToList();
but i got NullReferenceException error
var xdoc = XDocument.Load("CountriesXML.xml");
var videos = from f in xdoc.Descendants("iframe")
select new {
Src = (string)f.Attribute("src"),
Width = (int)f.Attribute("width"),
Height = (int)f.Attribute("height")
};
Or with your updated code:
var xdoc = XDocument.Load("CountriesXML.xml");
var data = from c in xdoc.Descendants("Category") // you have Category element
select new CountryData {
url = (string)c.Element("article") // there is also article element
.Element("videourl")
.Elements("iframe")
.Single().Attribute("src")
};

Can I 'flatten' an XDocument with Linq?

I have a heavily nested XML document that I need to load into my db for additional processing. For various reasons beyond the scope of this discussion I need to 'flatten' that structure down, then load it into a DataTables and then I can SQLBulkCopy it into the db where it will get processed. So assume my original XML looks something like this (mine is even more heavily nested, but this is the basic idea):
<data>
<report id="1234" name="XYZ">
<department id="234" name="Accounting">
<item id="ABCD" name="some item">
<detail id="detail1" value="1"/>
<detail id="detail2" value="2"/>
<detail id="detail3" value="3"/>
</item>
</department>
</report>
</data>
and I want to flatten that down into a single (albeit redundant) table structure where each attribute becomes a column (i.e. ReportId, ReportName, DepartmentId, DepartmentName, ItemId, ItemName, Detail1, Detail2, Detail3).
So my question is simply 'is it possible to accomplish this with a simple Linq query'? In the past I would just write some XSLT and be done with it but I'm curious if the Linq library can accomplish the same thing?
thanks!
Is this what you're looking for?
var doc = XDocument.Load(fileName);
var details =
from report in doc.Root.Elements("report")
from department in report.Elements("department")
from item in department.Elements("item")
from detail in item.Elements("detail")
select new
{
ReportId = (int)report.Attribute("id"),
ReportName = (string)report.Attribute("name"),
DepartmentId = (int)department.Attribute("id"),
DepartmentName = (string)department.Attribute("name"),
ItemId = (string)item.Attribute("id"),
ItemName = (string)item.Attribute("name"),
DetailId = (string)detail.Attribute("id"),
DetailValue = (int)detail.Attribute("value"),
};
If you want it as a DataTable, you can use the following extension method:
public static DataTable ToDataTable<T>(this IEnumerable<T> source)
{
PropertyInfo[] properties = typeof(T).GetProperties()
.Where(p => p.CanRead && !p.GetIndexParameters().Any())
.ToArray();
DataTable table = new DataTable();
foreach (var p in properties)
{
Type type = p.PropertyType;
bool allowNull = !type.IsValueType;
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
allowNull = true;
type = Nullable.GetUnderlyingType(type);
}
DataColumn column = table.Columns.Add(p.Name, type);
column.AllowDBNull = allowNull;
column.ReadOnly = !p.CanWrite;
}
foreach (var item in source)
{
DataRow row = table.NewRow();
foreach (var p in properties)
{
object value = p.GetValue(item, null) ?? DBNull.Value;
row[p.Name] = value;
}
table.Rows.Add(row);
}
return table;
}
Use it like this:
var table = details.CopyToDataTable();

Resources