Friday, March 28, 2008

Setting and Getting Documet Quality in MarkLogic Server

I had a request to influence the score of documents returned by searches based on the year of publication. Since a year isn't used as part of most searches, it seemed like the best approach was to set the document quality to the pub year. Then I could use that value in the scoring calculations. Take a look at the Developer's Guide for how do do that.

Here's how I looped through all the documents in a collection and set document quality using a value stored in the XML already.

(: Set document quality :)
for $i in collection('myCollection')/book
return
let $myYear := $i/metadata/publication-date
let $myBaseUri := base-uri($i)
(: I don't know about you, but I don't trust my XML vendors.
This tests casting the data to an int first. :)
let $myDocumentQuality :=
if($myYear castable as xs:integer) then
$myYear cast as xs:integer
else 1990 (: This is a default setting in case of bad data. :)
return

xdmp:document-set-quality($myBaseUri, $myDocumentQuality)

Depending on how many documents you have stored, you may need to modify this to set the document quality in smaller batches because it is quite intensive.

Once that's run, you can go back and review all your document quality settings. I originally had these two queries run together, but I think it takes a minute or two (depending on your system) for the settings to actually be indexed.

(: Get document quality :)
<results>
{
for $i in collection('myCollection')/book
return
<result
base-uri="{base-uri($i)}"
year="{$i/metadata/publication-date}"
set-document-quality="{xdmp:document-get-quality(base-uri($i))}"/>
}
</results>

Monday, March 17, 2008

Bad CodepointIterator::_next Error from MarkLogic Server

Getting a "Bad CodepointIterator::_next" error from a query in MarkLogic? Check you configuration at the app server level.

We were seeing this error on our production instance, but not our development instance. In our ASP.NET app (through XCC) we would see a generic message, but when we ran the query in cq we saw the more specific version. After a wasted day or so trying to track down the root cause, we thought we had it narrowed down to UTF-8 characters, but we couldn't explain why it was fine on one server but not the other. Then on a whim I compared the configurations looking for anything out of sync and noticed that the "collation" field on the server where we had the error was set to "http://marklogic.com/collation" while on our development instance it was "http://marklogic.com/collation/codepoint". We set them both to use /codepoint and the problem was solved.

If you're using both the HTTP and XDBC layers, make sure to check them both. We fixed it on our HTTP layer, which eliminated the bug in cq, but we were still getting it in the ASP.NET application until we changed the setting in both.

Sunday, March 16, 2008

Basic Information on MarkLogic Collections

I wanted to get some quick information about all of my collections, starting with a list of names and how many documents were in each. This isn't rocket science, but I'll add to this post as the query expands.

(: Set "collection lexicon" to true in the index definition. :)
let $collections := cts:collections() return
<root count="{ fn:count($collections) }">
{
for $collection in $collections
return
<collection>
<name> { $collection } </name>
<size> { fn:count(fn:collection($collection)) } </size>
</collection>
}
</root>

Wednesday, March 12, 2008

Hiding the Finish Button on an ASP.NET Wizard Control

This is a Class 1 stupid hack. I think it's one of those things where there probably is an easier way to do this, but I haven't found it yet. On an ASP.NET Wizard control, I wanted to hide the Finish button. Here's one way.
myWizard.FinishCompleteButtonType = ButtonType.Link;
myWizard.FinishCompleteButtonText = String.Empty;
myWizard.FinishCompleteButtonStyle.CssClass = "";
myWizard.FinishCompleteButtonStyle.BorderColor = System.Drawing.Color.White;
Another way is to create a CSS class that will hide the button and then assign that to the CssClass property.

Save to Word in ASP.NET

I was looking around for a code snippet to allow a user to save / export a print version of a web page to a Word file on the fly. This didn't need to be complex at all, but everything I found was either overwrought or would result in a Word document that displayed HTML tagging.

Here's an oversimplified method that works. You can build this up as needed. NOTE: Be sure to eliminate the extra space in the HTML tags below. Blogger's not behaving for me, so I had to insert them.

protected void Button1_Click(object sender, EventArgs e)
{
// Clear the response of any output
Response.Clear();
// Buffer this so the document comes down in one piece
Response.Buffer = true;
// You can do Excel, too, but I haven't tried that yet
Response.ContentType = "application/msword";
// You can use a variable instead of MyDoc, of course
Response.AddHeader("Content-Disposition", "inline;filename=MyDoc.doc");

StringBuilder myBuilder = new StringBuilder();
// This is the key bit versus other examples I've seen
// Adding the opening < html> etc forces Word to open this in Web Layout view
// If you use inline CSS, you can do much more
myBuilder.Append("< html>< head>< /head>< body>");
myBuilder.Append("< h1>Document Head< /h1>");
myBuilder.Append("< p>< strong>" + Label1.Text + "< /strong>< /p>");
myBuilder.Append("< p>" + TextBox1.Text + "< /p>");
// Do whatever else you need and append it as a string
myBuilder.Append("< /body>< /html>");

Response.Write(myBuilder);
Response.End();
Response.Flush();
}

Saturday, March 1, 2008

Save an ASP.NET TreeView Control to an XML File

Here are two methods to help you take a TreeView web control and save / export / serialize it to a file. Add these two statements to the top of the file:
using System.Xml;
using System.Text;
These two methods take the TreeView and recursively loop through all the TreeNode controls.
/// <summary>
/// Recurses through a TreeView web control exports the results
/// to an XML file nested to match the TreeView. Properties are
/// saved as attributes when a value is set.
/// <summary>
/// <param name="myTreeView">A TreeView web control.</param>
/// <param name="myFile">A file path and name with extension.</param>
protected void TreeViewToXml(System.Web.UI.WebControls.TreeView
myTreeView, string myFile)
{
try
{
XmlTextWriter xmlWriter = new XmlTextWriter(myFile, Encoding.UTF8);
xmlWriter.Formatting = Formatting.Indented;
xmlWriter.Indentation = 3;
xmlWriter.WriteStartDocument();
// Opens <TreeView>, the root node.
xmlWriter.WriteStartElement("TreeView");

// Go through child nodes.
ProcessNodes(myTreeView.Nodes, xmlWriter);

// </TreeView>, the root node.
xmlWriter.WriteEndElement();
xmlWriter.WriteEndDocument();

// Closes the object and saves the document to disk.
xmlWriter.Close();
}
catch
{
throw; // Throw any exceptions up the line.
}
}

/// <summary>
/// Recursively processes each TreeNode within a TreeNodeCollection
/// from a TreeView web control.
/// </summary>
/// <param name="myTreeNodes">A TreeNodeCollection from a TreeView web
/// control.</param>
/// <param name="myWriter">A XmlTextWriter being used to hold the XML
/// results from the TreeViewToXml method.</param>
protected void ProcessNodes(TreeNodeCollection myTreeNodes,
XmlTextWriter myWriter)
{
foreach (TreeNode thisNode in myTreeNodes)
{
myWriter.WriteStartElement("TreeNode"); // <TreeNode>.

// Go through each property and set it as an attribute if it exists.
if(!String.IsNullOrEmpty(thisNode.Text))
myWriter.WriteAttributeString("Text", thisNode.Text);
if (!String.IsNullOrEmpty(thisNode.ImageToolTip))
myWriter.WriteAttributeString("ImageToolTip",
thisNode.ImageToolTip);
if (!String.IsNullOrEmpty(thisNode.ImageUrl))
myWriter.WriteAttributeString("ImageUrl", thisNode.ImageUrl);
if (!String.IsNullOrEmpty(thisNode.NavigateUrl))
myWriter.WriteAttributeString("NavigateUrl",
thisNode.NavigateUrl);
if (!String.IsNullOrEmpty(thisNode.SelectAction.ToString()))
myWriter.WriteAttributeString("SelectAction",
thisNode.SelectAction.ToString());
if (!String.IsNullOrEmpty(thisNode.Target))
myWriter.WriteAttributeString("Target", thisNode.Target);
if (!String.IsNullOrEmpty(thisNode.ToolTip))
myWriter.WriteAttributeString("ToolTip", thisNode.ToolTip);
if (!String.IsNullOrEmpty(thisNode.Value))
myWriter.WriteAttributeString("Value", thisNode.Value);
if (!String.IsNullOrEmpty(thisNode.ValuePath))
myWriter.WriteAttributeString("ValuePath", thisNode.ValuePath);
if (thisNode.ShowCheckBox.HasValue)
myWriter.WriteAttributeString("ShowCheckBox",
thisNode.ShowCheckBox.ToString());
if (thisNode.Expanded.HasValue)
myWriter.WriteAttributeString("Expanded",
thisNode.Expanded.ToString());
myWriter.WriteAttributeString("Selected",
thisNode.Selected.ToString());
myWriter.WriteAttributeString("Checked",
thisNode.Checked.ToString());

// Recurse through any child nodes.
if (thisNode.ChildNodes.Count > 0)
ProcessNodes(thisNode.ChildNodes, myWriter);

myWriter.WriteEndElement(); // </TreeNode>.
}
}