How can I insert a cross-reference page number in Novacode DocX - c#

I am creating a Word document using Novacode DocX in which I want to insert a piece of text, and then later on in the document insert a reference to it in the form of '(see page X)' where X is dynamically generated by Word.
In Word itself, I can easily do this by creating a bookmark for the first piece of text and inserting a cross-reference where I want the page number.
I think I know how to add a bookmark using DocX, but how do I create the cross-reference? Is this even possible in DocX?
Many thanks in advance for your help,
Chris

After some fiddling, I finally found a way to achieve this:
internal void AddCrossReference(DocX doc, Paragraph p, string destination)
{
XNamespace ns= doc.Xml.Name.NamespaceName;
XNamespace xmlSpace = doc.Xml.GetNamespaceOfPrefix("xml");
p = p.Append(" (see pp");
p.Xml.Add(new XElement(ns + "r", new XElement(ns + "fldChar", new XAttribute(ns + "fldCharType", "begin"))));
p.Xml.Add(new XElement(ns + "r", new XElement(ns + "instrText", new XAttribute(xmlSpace + "space", "preserve"), String.Format(" PAGEREF {0} \\h ", destination))));
p.Xml.Add(new XElement(ns + "r", new XElement(ns + "fldChar", new XAttribute(ns + "fldCharType", "separate"))));
p.Xml.Add(new XElement(ns + "r", new XElement(ns + "rPr", new XElement(ns + "noProof")), new XElement(ns + "t", "1")));
p.Xml.Add(new XElement(ns + "r", new XElement(ns + "fldChar", new XAttribute(ns + "fldCharType", "end"))));
p = p.Append(")");
}
destination is the name of the bookmark you want to cross-reference.
Any suggested improvements would be most welcome.

Related

Cannot create XML where the XElement value contains ":"

I have the following code:
private string GetXmlBody()
{
XNamespace ns = "http://schemas.xmlsoap.org/soap/envelope/";
XNamespace xsdNs = "http://www.w3.org/2001/XMLSchema";
XNamespace xsiNs = "http://www.w3.org/2001/XMLSchema-instance";
XNamespace outNs = "http://soap.sforce.com/2005/09/outbound";
XNamespace sfNs = "urn:sobject.enterprise.soap.sforce.com";
XDocument requestXml = new XDocument(
new XElement(ns + "Envelope", new XAttribute(XNamespace.Xmlns + "soapenv", ns), new XAttribute(XNamespace.Xmlns + "xsd", xsdNs), new XAttribute(XNamespace.Xmlns + "xsi", xsiNs),
new XElement(ns + "Body",
new XElement(outNs + "notifications", new XAttribute("xmlns", outNs),
new XElement(outNs + "OrganizationId", runInfo.OrgId),
new XElement(outNs + "SessionId", runInfo.SessionId),
new XElement(outNs + "EnterpriseUrl", runInfo.Location),
new XElement(outNs + "Notification",
new XElement(outNs + "Id", "04l0H000014TY73QAG"),
new XElement(outNs + "sObject", new XAttribute(XNamespace.Xmlns + "sf", sfNs), new XAttribute(xsiNs + "type", "sf:" + runInfo.RecordType),
new XElement(sfNs + "Id", runInfo.RecordId),
new XElement(sfNs + runInfo.Field, runInfo.FieldValue)
)
)
)
)
)
);
return requestXml.ToString();
}
Which will generate XML needed however I'm running into the following error:
System.Xml.XmlException : The ':' character, hexadecimal value 0x3A, cannot be included in a name.
due to the value of runInfo.FieldValue which contains :. For Example the value may look like:
Opportunity:006i000000sidsh;Account:;userId:a016S00000sjsiq;sandbox:true
So far all the solutions or similar problems that I've seen revolve around producing the correct element name, my problem is around the value. If for instance I remove the : from the runInfo.FieldValue variable then the expected XML is produced.
Any thoughts on how to get around this? I've tried URL encoding the string but that just leads to a similar error except it complains about % values.

Linq to XML create elements with unique parent

Having this Linq statement to create an XML
new XElement(ns + "SpecialRegisters",
from reg in registers
where reg.IsUpdateRegister
select new XElement(ns + "UpdateRegisters",
new XElement(ns + "RegID",
new XAttribute("ID", registers.IndexOf(reg).ToString().PadLeft(2, '0'))
)
)
)
Is it possible to create a unique UpdateRegisters element with multiple RegID.
If there are no update registers there should not exist any UpdateRegisters element.
You could do this instead:
var result=new XElement(ns + "SpecialRegisters");
var updateRegister=register.Where(e=>e.IsUpdateRegister);// To not repeat the same query twice
if(updateRegister.Count()>0)
{
result.Add( new XElement(ns + "UpdateRegisters",
updateRegister.Select((e,i)=> new XElement(ns + "RegID",i));
}
Update
If you want to do it all in the same statement then you could do this:
var updateRegister=register.Where(e=>e.IsUpdateRegister);// To not repeat the same query twice
var result=new XElement(ns + "SpecialRegisters", updateRegister.Any()?
new XElement(ns + "UpdateRegisters", updateRegister.Select((e,i)=> new XElement(ns + "RegID",i)):null );
My solution based on yours (miguelmpn)
Speed is not important, as I'm saving a simple and small file..
new XElement(ns + "SpecialRegisters", Registers.Registers.Singleton.Any(x => x.IsUpdateRegister) ?
new XElement(ns + "UpdateRegisters", Registers.Registers.Singleton.Where(e => e.IsUpdateRegister).Select((e, i)=> new XElement(ns + "RegID", i))) : null)
I think the one of Enumerable.Aggregate method overload fits for this task very well.
var specialRegisters =
new XElement(ns + "SpecialRegisters",
Registers.Registers.Singleton.Where(reg => reg.IsUpdateRegister)
.Select((reg, i) => new XElement(ns + "RegID", i))
.Aggregate(new XElement(ns + "UpdateRegisters"),
(upReg, reg) =>
{
upReg.Add(reg);
return upReg;
},
upReg => upReg.HasElements ? upReg : null));
This approach will enumerate collection only once.
Third parameter of Aggregate method is key value of this approach, which return null in case no elements was added.
My own opinion to comment: but my intention is to keep the code cleaner by having it all on the same linq query.
I think keeping this kind of logic in one statement is not clean approach. I think one of idea of cleanliness is that reader of your code will understand your intentions faster.
You can extract creating UpdateRegisters element to the dedicated method
private XElement CreateUpdateRegistersOrNullIfEmpty(XNamespace ns,
IEnumerable<YourType> data)
{
return data.Where(reg => reg.IsUpdateRegister)
.Select((reg, i) => new XElement(ns + "RegID", i))
.Aggregate(new XElement(ns + "UpdateRegisters"),
(upReg, reg) =>
{
upReg.Add(reg);
return upReg;
},
upReg => upReg.HasElements ? upReg : null));
}
Then use it
var specialRegisters =
new XElement(ns + "SpecialRegisters",
CreateUpdateRegistersOrNullIfEmpty(ns, Registers.Registers.Singleton));

Unassigned local variable/ alter XML Format

My program takes user input and uses it to create a query. The results of that query will then be put into an XML file with XElements based on their selections. I have a string staff which i set equal to the value of staffType.
Where staff is declared w/ snippet of query code:
using (
var conn = new SqlConnection("Server=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;Database=KUDERDEV;User ID=xxxxxxxxxxxxxxxxxx;Password= xxxxxxxx;Trusted_Connection=False;Encrypt=True;Connection Timeout=30;")
)
{
conn.Open();
bool quit = false;
string choice;
string staff;
SqlCommand cmd = new SqlCommand();
while (!quit)
{
Console.WriteLine("Sort by staffType: K12Staff, psStaff, WFStaff or none?");
string staffType = Console.ReadLine();
staff = staffType;
if (staffType == "K12Staff")
{
Console.WriteLine("Sort by code, date, both, or none?");
choice = Console.ReadLine();
switch (choice)
{
case "code":
Console.WriteLine("Sort by code1 or code2?");
string codeCol = Console.ReadLine();
Console.WriteLine("Enter desired code");
string code = Console.ReadLine();
cmd = new SqlCommand("SELECT * FROM Staff WHERE (Staff." + #codeCol + "='" + #code + "') AND staffType ='" + #staffType + "' FOR XML PATH('staff'), ROOT('k12Staff')", conn);
quit = true;
staff = staffType;
break;
When the query string is complete, I go into another using statement without closing the first one to write the XML file. Here I want to change the format of the XML (XElement) depending on which staffType was chosen.
Writing the XML file snippet:
using (cmd)
{
using (var reader = cmd.ExecuteXmlReader())
{
var doc = XDocument.Load(reader);
string path = #"Staff." + DateTime.Now.ToString("yyyyMMdd") + ".xml";
using (var writer = new StreamWriter(path))
{
//if (staff == "k12Staff")
XNamespace ns = "http://specification.sifassociation.org/Implementation/na/3.2/html/CEDS/K12/K12_k12Staff.html";
var root = new XElement(ns + "k12Staff");
foreach (var d in doc.Descendants("staff"))
{
root.Add(new XElement(ns + "staff",
new XElement(ns + "identity",
new XElement(ns + "name",
new XElement(ns + "firstName", first),
new XElement(ns + "lastName", last)
)
),
new XElement(ns + "employment",
new XElement(ns + "positionTitle", position)
),
new XElement(ns + "assignment",
new XElement(ns + "leaID", leaID),
new XElement(ns + "schoolID", schoolID)
),
new XElement(ns + "contact",
new XElement(ns + "phoneNumberList",
new XElement(ns + "number", phone),
new XElement(ns + "phoneNumberIndicator", indicator)
),
new XElement(ns + "emailList",
new XElement(ns + "email", email)
)
),
new XElement(ns + "delete", delete)
Then if the staffType was something different such as "psStaff" then I would change the format of the XML (XElement) to have different names, positions, etc.
So it would be like:
if(staff == "k12Staff"){
format xml here...
}
else if (staff == "psStaff"){
format xml here....
}
etc etc..
My Problem:
In the previous example, my code has if(staff == "k12Staff") but I am told that it is an assigned local variable. I have tried declaring staff outside of the using statements as well as trying to use staff in a using statement like so using(staff) which is how I used my cmd variable. How come my program recognizes cmd but not staff?
You did not post the entire code, so it is not possble to say, where your variable staff should get a value and why it doesn't.
Not assigned means: existing but never got a value...
Try the following: At the place, where you declare your variable change this:
string staff="defaultValue";
On the place where you handle the variable's content change this:
if(staff == "k12Staff"){
format xml here...
}
else if (staff == "psStaff"){
format xml here....
}
else if (staff == "defaultValue"){
//What ever is to be done if all attempts to set "staff" did not work
//You must set a stop mark before your `while (!quit)` and step through.
//There is at least one situation, where `staff` is not set to any value...
//If there's a bug you must fix this, if this is allowed to happen, solve it here...
}

generating KML file using C# and LINQ with a recursive function

I am trying to create a KML file dynamically in C#. I have wrote a recursive function to do this. However the result of output has a bit problem. The problem is the position of closing tags of all placemarks. I am really confused. Please tell me where am I making mistake in the recursive function???
My code:
private void xmlBuild()
{
XDocument doc = new XDocument(
new XDeclaration("1.0", "utf-8", ""),
new XComment("This is comment by me"),
new XElement(ns+"kml",
new XElement(ns+"Document", rec_build())));
doc.Save(Server.MapPath(#"~\App_Data\markers2.xml"));
}
private XElement rec_build()
{
if (iteration != 0)
{
iteration -= 1;
return final_rec = new XElement(ns + "Placemark",
new XAttribute("id", "1"),
new XElement(ns + "title", "something"),
new XElement(ns + "description", "something"),
new XElement(ns + "LookAt",
new XElement(ns + "Longitude", "49.69"),
new XElement(ns + "Latitude", "32.345")), new XElement(ns + "Point", new XElement(ns + "coordinates", "49.69,32.345,0")),rec_build());
}
else
{
return null;
}
}
and this is the output for iteration value of 2: (please notice the closing tags of placemark id=1 at the end of file. It should be before the starting tag of placemark id=2!
<?xml version="1.0" encoding="utf-8"?>
<!--This is comment by me-->
<kml xmlns="http://earth.google.com/kml/2.2">
<Document>
<Placemark id="1">
<title>something</title>
<description>something</description>
<LookAt>
<Longitude>49.69</Longitude>
<Latitude>32.345</Latitude>
</LookAt>
<Point>
<coordinates>49.69,32.345,0</coordinates>
</Point>
<Placemark id="1">
<title>something</title>
<description>something</description>
<LookAt>
<Longitude>49.69</Longitude>
<Latitude>32.345</Latitude>
</LookAt>
<Point>
<coordinates>49.69,32.345,0</coordinates>
</Point>
</Placemark>
</Placemark>
</Document>
</kml>
So the problem is each time you recurse, you are adding the element to the newly created item. It seems that a loop would work better.
Essentially code is doing this:
set up the kml outbody
1st call and add element (element 1) to kml outerboy
2nd call add element (element 2) to (element 1)
3rd call add element (element 3) to (element 2).
If you wanted to do a recursive method rather then the looping mechanism, pass in a reference to the outer kml .
The recursive is more confusing if this is exactly how it works
(Sorry if I have an extra or missing parenthesis, comma, or other item. I don't have VS installed on this)
Looping:
private void xmlBuild()
{
XElement documentElement = new XElement(ns + "Document");
for (int i = 0; i < 2; i++)
{
documentElement.Add(rec_build());
}
XDocument doc = new XDocument(
new XDeclaration("1.0", "utf-8", ""),
new XComment("This is comment by me"),
new XElement(ns + "kml", documentElement));
doc.Save(Server.MapPath(#"~\App_Data\markers2.xml"));
}
private XElement rec_build()
{
return new XElement(ns + "Placemark",
new XAttribute("id", "1"),
new XElement(ns + "title", "something"),
new XElement(ns + "description", "something"),
new XElement(ns + "LookAt",
new XElement(ns + "Longitude", "49.69"),
new XElement(ns + "Latitude", "32.345")),
new XElement(ns + "Point",
new XElement(ns + "coordinates", "49.69,32.345,0")));
}
Recursive:
private void xmlBuild()
{
XElement docElement = new XElement(ns+"Document");
rec_build(docElement);
XDocument doc = new XDocument(
new XDeclaration("1.0", "utf-8", ""),
new XComment("This is comment by me"),
new XElement(ns+"kml", docElement)));
doc.Save(Server.MapPath(#"~\App_Data\markers2.xml"));
}
private XElement rec_build(XElement doc)
{
if (iteration != 0)
{
iteration -= 1;
doc.Add(new XElement(ns + "Placemark",
new XAttribute("id", "1"),
new XElement(ns + "title", "something"),
new XElement(ns + "description", "something"),
new XElement(ns + "LookAt",
new XElement(ns + "Longitude", "49.69"),
new XElement(ns + "Latitude", "32.345")),
new XElement(ns + "Point", new XElement(ns + "coordinates", "49.69,32.345,0")));
return recBuild(doc);
}
else
{
return null;
}
}
You are adding the recursively build elements as children of Placemark and not Document. This should do the trick:
private void xmlBuild()
{
XElement docElement = new XElement(ns + "Document");
XDocument doc = new XDocument(
new XDeclaration("1.0", "utf-8", ""),
new XComment("This is comment by me"),
new XElement(ns + "kml", docElement));
rec_build(docElement);
doc.Save(Server.MapPath(#"~\App_Data\markers2.xml"));
}
private XElement rec_build(XElement docElement)
{
if (iteration != 0)
{
iteration -= 1;
return final_rec = new XElement(ns + "Placemark",
new XAttribute("id", "1"),
new XElement(ns + "title", "something"),
new XElement(ns + "description", "something"),
new XElement(ns + "LookAt",
new XElement(ns + "Longitude", "49.69"),
new XElement(ns + "Latitude", "32.345")),
new XElement(ns + "Point", new XElement(ns + "coordinates", "49.69,32.345,0")));
docElement.Add(final_rec);
rec_build(docElement);
}
else
return null;
}

How to generate xsi:schemalocation attribute correctly when generating a dynamic sitemap.xml with LINQ to XML?

I am generating a dynamic sitemap.xml
According to sitemaps.org this is the header for a sitemap.xml
<?xml version='1.0' encoding='UTF-8'?>
<urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"
xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
...
</url>
</urlset>
So I'm using LINQ To XML to generate the sitemap.xml
XNamespace ns = "http://www.sitemaps.org/schemas/sitemap/0.9";
return new XElement(ns + "urlset",
new XAttribute("xmlns", "http://www.sitemaps.org/schemas/sitemap/0.9"),
new XAttribute(XNamespace.Xmlns + "xsi", "http://www.w3.org/2001/XMLSchema-instance"),
//new XAttribute("xsi:schemaLocation", "http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"),
from node in new GetNodes()
select new XElement(ns + "url",
new XElement(ns + "loc", node.Loc),
new XElement(ns + "lastmod", node.LastMod),
new XElement(ns + "priority", node.Priority)
)
).ToString();
The commented line is the one i cannot get right.
How can i set the "xsi:schemalocation" attribute?
Thanks.
Ok, I got it right. Thanks to Mike Caron
If I declare the XAtrribute(XNamespace.Xmlns + "xsi",...) then it works
XNamespace ns = "http://www.sitemaps.org/schemas/sitemap/0.9";
XNamespace xsi = "http://www.w3.org/2001/XMLSchema-instance";
return new XElement(ns + "urlset",
new XAttribute("xmlns", "http://www.sitemaps.org/schemas/sitemap/0.9"),
new XAttribute(XNamespace.Xmlns + "xsi", "http://www.w3.org/2001/XMLSchema-instance"),
new XAttribute(xsi + "schemaLocation", "http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"),
from node in GetNodes()
select new XElement(ns + "url",
new XElement(ns + "loc", node.Loc),
new XElement(ns + "lastmod", node.LastMod),
new XElement(ns + "priority", node.Priority)
)
).ToString();
I don't know LINQ to XML, but after a quick peek at the documentation, try this:
XNamespace ns = "http://www.sitemaps.org/schemas/sitemap/0.9";
XNamespace xsi = "http://www.w3.org/2001/XMLSchema-instance";
return new XElement(ns + "urlset",
new XAttribute(xsi + "schemaLocation", "http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"),
from node in new GetNodes()
select new XElement(ns + "url",
new XElement(ns + "loc", node.Loc),
new XElement(ns + "lastmod", node.LastMod),
new XElement(ns + "priority", node.Priority)
)
).ToString();
Note that I'm not setting the xmlns attributes explicitly. I suspect they're generated automatically. Also, caveat emptor, since this is not tested.

Categories