Compartir a través de


Este artículo proviene de un motor de traducción automática.

SharePoint y Open XML

Generar documentos de SharePoint con controles de contenido XML abierto

Eric White

Descargar el ejemplo de código

A menudo se da el caso de que un administrador de departamento necesita enviar periódicamente un informe de estado con un buen formato a su director general o que un líder del equipo necesita enviar un informe de estado semanal a un número de partes interesadas. Para colaborar con otros usuarios en sus organizaciones, el director y el líder de equipo pueden mantener información de estado en las listas de SharePoint. La pregunta para los desarrolladores es cómo incluir la información en las listas de un documento como un informe de estado.

XML abierto, el formato de archivo predeterminado para Office 2007, es un estándar ISO (documentado en detalle preciso en IS29500). Sencillamente, los archivos XML abiertos son archivos zip que contienen XML y es muy fácil generar o modificar documentos XML abiertos mediante programación. Todo que necesita es una biblioteca para abrir un archivo zip y una API XML. Mediante las características de programación de XML abierto y SharePoint, puede elaborar un pequeño sistema de generación de documentos que aproveche los controles de contenido XML abierto y coloque a las personas como el director y el líder de equipo a cargo de sus informes. En este artículo presentaré algunas instrucciones y código de ejemplo para crear un sistema de generación de documentos que utilice listas de SharePoint para rellenar las tablas de un documento de procesamiento de textos de XML abierto.

Información general sobre el ejemplo

El ejemplo de este artículo es un pequeño elemento Web de SharePoint que genera un documento de procesamiento de textos de XML abierto a partir de los datos que residen en las listas de SharePoint. He creado dos listas de SharePoint personalizadas, que se muestran en la Figura 1, que contienen los datos que deseamos insertar en las tablas.


Figura 1 Dos listas de SharePoint personalizadas

También he creado un documento de procesamiento de textos de XML abierto de plantilla que contiene controles de contenido que definen las listas y columnas que son el origen de datos para el documento generado. Los controles se muestran en la Figura 2.


Figura 2 El documento XML abierto de plantilla con controles de contenido

Por último, creé un elemento Web que recupera la lista de documentos de plantilla desde una biblioteca de documentos específica y presenta la lista a los usuarios. Un usuario selecciona un elemento en la lista y, a continuación, hace clic en el botón Generar informe. El elemento Web crea un documento de procesamiento de textos de XML abierto, lo coloca en la biblioteca de documentos de informes y redirige al usuario a esa biblioteca para que pueda abrir el informe. El elemento Web se muestra en la Figura 3 y el documento que genera se muestra en la Figura 4.


Figura 3 Un elemento Web de SharePoint que permite a los usuarios seleccionar un documento de plantilla

Controles de contenido XML abierto

Antes de describir la solución de SharePoint, trataré los conceptos básicos de controles de contenido XML abierto. Los controles de contenido XML abierto proporcionan una utilidad en los documentos de procesamiento de textos que le permite definir el contenido y asociar metadatos a ese contenido. Para utilizar controles de contenido, debe habilitar la ficha Programador en Microsoft Office Word 2007. (Haga clic en el menú de Office; Opciones de Word; a continuación, en el cuadro de diálogo Opciones de Word, seleccione la ficha Mostrar programador en la cinta de opciones.)

Para insertar un control de contenido, seleccione el texto y haga clic en el botón en el área Controles de la ficha Programador que crea un control de contenido de texto sin formato, que se muestra en la Figura 5.


Figura 4 Un documento de procesamiento de textos de XML abierto que contiene el informe generado


Figura 5 Utilizar este botón para crear un control de contenido de texto sin formato

Puede establecer propiedades para un control de contenido para darle un título y asignarle una etiqueta. Haga clic en el control de contenido y, a continuación, haga clic en el botón Propiedades del área Controles en la ficha Programador. Esto muestra un cuadro de diálogo que se puede utilizar para establecer el título y la etiqueta.

Los controles de contenido utilizan el elemento w:sdt en formato XML abierto, que se muestra en la Figura 6. El contenido del control de contenido se define en el elemento w:sdtContent. En la figura, también puede ver el título del control de contenido en el elemento w:alias y la etiqueta de control de contenido en el elemento w:tag.

Programación de XML abierto con .NET Framework

Puede realizar una gran variedad de enfoques para la programación de XML abierto mediante Microsoft .NET Framework:

  • Utilice las clases de System.IO.Packaging
  • Utilice el SDK de formato XML abierto con cualquiera de las tecnologías de programación de XML disponibles en .NET, como XmlDocument, XmlParser o LINQ to XML. Mi favorito es LINQ to XML.
  • Utilice el modelo de objetos con establecimiento inflexible de tipos de SDK de formato XML abierto versión 2.0. Puede encontrar un número de artículos que presentan cómo programar con este modelo de objetos.

Aquí, voy a utilizar el SDK de formato XML abierto (cualquier versión 1.0 o 2.0) con LINQ to XML. Puede descargar el SDK de formato XML abierto de go.microsoft.com/fwlink/?LinkId=127912.

Resulta útil encapsular la funcionalidad de XML abierto alrededor de controles contenido en una clase ContentControlManager. Al enfocar el problema de esta forma, puede desarrollar las funcionalidades de XML abierto en una aplicación de consola simple. Después de haber codificado y depurado la funcionalidad de XML abierto, se la pueden incorporar en la característica de SharePoint con un mínimo de esfuerzo. Demora mucho tiempo producir la sobrecarga de implementar la característica de SharePoint mientras se depura código XML abierto.

Para nuestro ejemplo de generación de documentos de SharePoint, deseamos escribir algún código que recupere un documento de plantilla desde una biblioteca de documentos específicos, consulte el documento para los controles de contenido que contiene y utilice los metadatos almacenados en cada control de contenido para rellenar el documento con datos de la lista de SharePoint correspondiente.

Si descarga el ejemplo y examina el documento XML abierto de plantilla, verá que contiene un control de contenido que rodea cada tabla y que se insertan controles de contenido en cada celda de la fila inferior de cada tabla. La etiqueta para cada control de contenido en las celdas de la tabla especifica el nombre de una columna en las listas de SharePoint. Para mayor comodidad, también he establecido el título de cada control de contenido para el mismo valor que la etiqueta. Los controles de contenido muestran su título cuando el punto de inserción está dentro del control.

Al escribir el código de una característica de SharePoint que genera un documento XML abierto, el código debe consultar primero el documento para estos controles de contenido. La consulta devuelve un árbol XML que describe la estructura de los controles de contenido, junto con la etiqueta para cada uno. Si ejecuta este código en el documento de ejemplo, se produce el siguiente código XML:

<ContentControls>

  <Table Name="Team Members">

    <Field Name="TeamMemberName" />

    <Field Name="Role" />

  </Table>

  <Table Name="Item List">

    <Field Name="ItemName" />

    <Field Name="Description" />

    <Field Name="EstimatedHours" />

    <Field Name="AssignedTo" />

  </Table>

</ContentControls>

Este documento XML muestra qué listas de SharePoint tiene que consultar nuestro código. Para cada elemento de la lista, deberá recuperar los valores de las columnas especificadas. El código para consultar el documento de procesamiento de textos de XML abierto, que aparece en Figura 7, se escribe como una consulta LINQ to XML que utiliza la construcción funcional para formar el XML devuelto.

Para utilizar la construcción funcional, el código crea una instancia de un objeto XElement mediante su constructor, pasando una consulta LINQ to XML como un argumento para el constructor. La consulta LINQ to XML utiliza métodos de eje para recuperar los elementos apropiados en el cuerpo del documento y utiliza el método de extensión Enumerable.Select para formar XML nuevo de los resultados de la consulta. Para comprender sobre construcción funcional se necesita estudiar un poco, pero como puede ver, una vez que se entiende, se puede hacer mucho con sólo un poco de código.

Figura 6 Formato XML abierto para un control de contenido

<w:p>

<w:r>

<w:t xml:space="preserve">Not in content control. </w:t>

</w:r>

<w:sdt>

<w:sdtPr>

<w:alias w:val="Test"/>

<w:tag w:val="Test"/>

<w:id w:val="5118254"/>

<w:placeholder>

<w:docPart w:val="DefaultPlaceholder_22675703"/>

</w:placeholder>

</w:sdtPr>

<w:sdtContent>

<w:r>

<w:t>This is text in content control.</w:t>

</w:r>

</w:sdtContent>

</w:sdt>

<w:r>

<w:t xml:space="preserve"> Not in content control.</w:t>

</w:r>

</w:p>

Preatomización de objetos XName y XNamespace

El código de la Figura 7 utiliza un método denominado "preatomización" de los nombres de LINQ to XML. Ésta es sólo una forma decorativa de decir que escribe una clase estática (vea la Figura 8) que contiene los campos estáticos que se inicializan en los nombres completos de los elementos y atributos que utiliza.

Figura 7 Recuperar la estructura de los controles de contenido en el documento de plantilla

public static XElement GetContentControls(

WordprocessingDocument document)

{

XElement contentControls = new XElement("ContentControls",

document

.MainDocumentPart

.GetXDocument()

.Root

.Element(W.body)

.Elements(W.sdt)

.Select(tableContentControl =>

new XElement("Table",

new XAttribute("Name", (string)tableContentControl

.Element(W.sdtPr).Element(W.tag).Attribute(

W.val)),

tableContentControl

.Descendants(W.sdt)

.Select(fieldContentControl =>

new XElement("Field",

new XAttribute("Name",

(string)fieldContentControl

.Element(W.sdtPr)

.Element(W.tag)

.Attribute(W.val)

)

)

)

)

)

);

return contentControls;

}

No hay una buena razón para inicializar objetos XName y XNamespace de esta manera. LINQ to XML abstrae los nombres y espacios de nombres XML en dos clases: System.Xml.Linq.XName y System.Xml.Linq.XNamespace, respectivamente. La semántica de estas clases incluye la noción de que si dos XNames tienen el mismo nombre completo (espacio de nombres + nombre local), estarán representados por el mismo objeto. Esto permite una rápida comparación de objetos XName. En lugar de utilizar una comparación de cadenas para seleccionar objetos XElement de un nombre determinado, el código sólo necesita comparar objetos. Cuando se inicializa un objeto XName, LINQ to XML busca primero en una caché para determinar si ya existe un objeto XName con el mismo espacio de nombres y nombre. Si existe uno, el objeto se inicializa en el objeto XName existente desde la caché. Si no existe uno, LINQ to XML inicializa uno nuevo y lo agrega a la caché. Como podrá imaginar, si este proceso se repite una y otra vez, puede provocar problemas de rendimiento. Inicializando estos objetos en una clase estática, el trabajo se realiza sólo una vez. Además, mediante esta técnica se reduce la posibilidad de que un nombre de elemento o atributo esté mal escrito en el cuerpo del código. Otra ventaja es que mediante esta técnica, obtiene soporte técnico de IntelliSense, que permite escribir programas XML abierto mediante LINQ to XML más fácil.

Figura 8 Una clase estática que contiene campos estáticos para preatomizar objetos XName y XNamespace

public static class W

{

public static XNamespace w =

"https://schemas.openxmlformats.org/wordprocessingml/2006/main";

public static XName body = w + "body";

public static XName sdt = w + "sdt";

public static XName sdtPr = w + "sdtPr";

public static XName tag = w + "tag";

public static XName val = w + "val";

public static XName sdtContent = w + "sdtContent";

public static XName tbl = w + "tbl";

public static XName tr = w + "tr";

public static XName tc = w + "tc";

public static XName p = w + "p";

public static XName r = w + "r";

public static XName t = w + "t";

public static XName rPr = w + "rPr";

public static XName highlight = w + "highlight";

public static XName pPr = w + "pPr";

public static XName color = w + "color";

public static XName sz = w + "sz";

public static XName szCs = w + "szCs";

}

Los métodos de extensión PutXDocument y GetXDocument

En el ejemplo presentado en este artículo también se utiliza otro pequeño truco para facilitar la programación y mejorar el rendimiento. El SDK de formato XML abierto tiene la capacidad de poner las anotaciones en partes del documento. Esto significa que puede adjuntar cualquier objeto de .NET Framework a un objeto OpenXmlPart y recuperarlo posteriormente especificando el tipo del objeto que ha vinculado.

Se pueden definir dos métodos de extensión, GetXDocument y PutXDocument, que utilizan anotaciones para minimizar la deserialización del XML de la parte XML abierto. Cuando llamamos GetXDocument, primero busca para ver si una anotación de tipo XDocument existe en OpenXmlPart. Si existe la anotación, GetXDocument la devuelve. Si no existe la anotación, el método rellena un XDocument de la parte, anota la parte y, a continuación, devuelve el XDocument nuevo.

El método de extensión PutXDocument también comprueba si existe una anotación de tipo XDocument. Si existe esta anotación, PutXDocument vuelve a escribir el XDocument (supuestamente modificado después de que el código llama a GetXDocument) en OpenXMLPart. Se muestran los métodos de extensión GetXDocument y PutXDocument en la Figura 9. Puede ver el uso del método de extensión GetXDocument en el método GetContentControls enumerado anteriormente en la Figura 7.

Figura 9 Los métodos de extensión utilizan anotaciones de SDK de formato XML abierto para minimizar la deserialización de XML

public static class AssembleDocumentLocalExtensions

{

public static XDocument GetXDocument(this OpenXmlPart part)

{

XDocument xdoc = part.Annotation<XDocument>();

if (xdoc != null)

return xdoc;

using (Stream str = part.GetStream())

using (StreamReader streamReader = new StreamReader(str))

using (XmlReader xr = XmlReader.Create(streamReader))

xdoc = XDocument.Load(xr);

part.AddAnnotation(xdoc);

return xdoc;

}

public static void PutXDocument(this OpenXmlPart part)

{

XDocument xdoc = part.GetXDocument();

if (xdoc != null)

{

// Serialize the XDocument object back to the package.

using (XmlWriter xw =

XmlWriter.Create(part.GetStream

(FileMode.Create, FileAccess.Write)))

{

xdoc.Save(xw);

}

}

}

}

Reemplazar los controles de contenido con datos

Ahora que tenemos un método que devuelve la estructura de los controles de contenido de las tablas y celdas, necesitamos un método (SetContentControls) que cree un documento XML abierto con datos específicos (recuperados las listas de SharePoint) insertados en las tablas. Se puede definir este método para que tome un árbol XML como argumento. El árbol XML se muestra en la Figura 10 y la Figura 11 muestra el documento que SetContentControls crea cuando se le pasa el árbol XML.

Figura 10 Un árbol XML que contiene datos para insertar en las tablas de documento de procesamiento de textos

<ContentControls>

<Table Name="Team Members">

<Field Name="TeamMemberName" />

<Field Name="Role" />

<Row>

<Field Name="TeamMemberName" Value="Bob" />

<Field Name="Role" Value="Developer" />

</Row>

<Row>

<Field Name="TeamMemberName" Value="Susan" />

<Field Name="Role" Value="Program Manager" />

</Row>

<Row>

<Field Name="TeamMemberName" Value="Jack" />

<Field Name="Role" Value="Test" />

</Row>

</Table>

<Table Name="Item List">

<Field Name="ItemName" />

<Field Name="Description" />

<Field Name="EstimatedHours" />

<Field Name="AssignedTo" />

<Row>

<Field Name="ItemName" Value="Learn SharePoint 2010" />

<Field Name="Description" Value="This should be fun!" />

<Field Name="EstimatedHours" Value="80" />

<Field Name="AssignedTo" Value=”All” />

</Row>

<Row>

<Field Name="ItemName" Value=

"Finalize Import Module Specification" />

<Field Name="Description" Value="Make sure to handle all document

formats." />

<Field Name="EstimatedHours" Value="35" />

<Field Name="AssignedTo" Value=”Susan" />

</Row>

<Row>

<Field Name="ItemName" Value="Write Test Plan" />

<Field Name=”Description" Value=

"Include regression testing items." />

<Field Name="EstimatedHours" Value="20" />

<Field Name="AssignedTo" Value="Jack" />

</Row>

</Table>

</ContentControls>


Figura 11 El documento generado

Puede ver que la única fila que contiene controles de contenido se ha reemplazado por varias filas, cada una con los datos del árbol XML que se pasaron como un argumento al método. Si utiliza un árbol XML para pasar los datos al código que manipula el formato XML abierto, consigue una buena separación del código que utiliza el modelo de objetos de SharePoint y el código XML abierto.

El código para armar el nuevo documento respeta cualquier formato aplicado a la tabla. Por ejemplo, si ha configurado la tabla para mostrar distintos colores para filas alternas, o si ha establecido el color de fondo de una columna, el documento recién generado refleja los cambios de formato.

Si descarga y examine el ejemplo de ContentControlManager, puede ver que el código obtiene una copia de la fila que contiene controles de contenido y la guarda como una fila prototipo:

// Determine the element for the row that contains the content controls.

// This is the prototype for the rows that the code will generate from data.

XElement prototypeRow = tableContentControl

    .Descendants(W.sdt)

    .Ancestors(W.tr)

    .FirstOrDefault();

A continuación, para cada elemento recuperado de la lista de SharePoint, el código clona la fila prototipo, modifica la fila clonada con los datos de la lista de SharePoint y la agrega a una colección que se inserta en el documento.

Después de crear la lista de las nuevas filas, el código quita la fila prototipo de la lista e inserta la colección de filas recién creadas, tal como se muestra aquí:

XElement tableElement = prototypeRow.Ancestors(W.tbl).First();

prototypeRow.Remove();

tableElement.Add(newRows);

Crear la característica de SharePoint

He utilizado la versión CTP de febrero de 2009 de las extensiones de Visual Studio 2008 para Windows SharePoint Services 3.0, v1.3 para generar este ejemplo. He integrado y ejecutado este ejemplo en versiones de 32 bits y de 64 bits de WSS. (Kirk Evans tiene algunas fabulosas difusiones web que muestran cómo utilizar estas extensiones.)

El ejemplo contiene el código para crear los controles de elementos Web. El código le resultará bastante claro si está acostumbrado a crear elementos Web de SharePoint. Cuando un usuario hace clic en el botón Generar informe, el código llama al método CreateReport, que ensambla el nuevo documento de procesamiento de textos de XML abierto del documento de plantilla mediante los datos de la lista de SharePoint según cómo estén configurados en las etiquetas de los controles de contenido. Hay algunos puntos para tener en cuenta sobre el código para el método CreateReport. Los archivos en una biblioteca de documentos de SharePoint se devuelven como matrices de bytes. Tiene que convertir esta matriz de bytes en una secuencia de memoria para que pueda abrir y modificar el documento mediante el SDK de formato XML abierto. Uno de los constructores de MemoryStream toma una matriz de bytes y usted podría ser tentado a utilizar ese constructor. Sin embargo, la secuencia de memoria creada con ese constructor es una secuencia de memoria que no se puede cambiar de tamaño y el SDK de formato XML abierto requiere que la secuencia de memoria se pueda cambiar de tamaño. La solución es crear una MemoryStream con el constructor predeterminado y, a continuación, escribir la matriz de bytes SharePoint en la MemoryStream, como se muestra en la Figura 12.

Figura 12 Escribir una matriz de bytes desde SharePoint en una MemoryStream

private ModifyDocumentResults CreateReport(SPFile file, Label message)

{

byte[] byteArray = file.OpenBinary();

using (MemoryStream mem = new MemoryStream())

{

mem.Write(byteArray, 0, (int)byteArray.Length);

try

{

using (WordprocessingDocument wordDoc =

WordprocessingDocument.Open(mem, true))

{

// Get the content control structure from the template

// document.

XElement contentControlStructure =

ContentControlManager.GetContentControls(wordDoc);

// Retrive data from SharePoint,

constructing the XML tree to

// pass to the ContentControlManager.SetContentControls

// method.

...

}

}

}

}

El resto del código es sencillo. Utiliza el modelo de objetos de SharePoint para recuperar bibliotecas de documentos y el contenido de las bibliotecas, recuperar listas y recuperar valores de columnas para cada fila en la lista. Ensambla el árbol XML que se va a pasar a ContentControlManager.SetContentControls y, a continuación, llama a SetContentControls.

El código ensambla el nombre del documento del informe generado como Informe-aaaa-mm-dd. Si el informe ya existe, el código anexa un número al nombre del informe para diferenciar el informe de otros informes que ya se han generado. Por ejemplo, si ya existe un Informe-2009-08-01.docx, el informe se escribe como Informe 2009 8-2 (1).docx.

Fácil personalización

Probablemente deseará personalizar este ejemplo según sus propias necesidades. Una posible mejora es permitir que un control de contenido en el cuerpo del documento de plantilla extraiga contenido reutilizable de un documento especificado almacenado en SharePoint. Podría escribir el código para poder colocar el nombre del documento que contiene el texto reutilizable como texto en el control de contenido.

Además, este ejemplo integra como parte del código los nombres de las bibliotecas de documentos TemplateReports y Reports. Puede quitar esta restricción especificando esta información en una lista de SharePoint. El código entonces conocería el nombre de esta lista de configuración solamente. Los nombres de las bibliotecas de documentos TemplateReports y Reports serían controlados por los datos de la lista de configuración.

SharePoint es una tecnología eficaz que facilita la colaboración de las personas de las organizaciones. XML abierto es una potente tecnología emergente que está cambiando la forma en que se generan documentos. El uso de las dos tecnologías juntas permite crear aplicaciones en las que las personas pueden utilizar documentos para colaborar de nuevas formas.

Eric White es un escritor de Microsoft especializado en los formatos de archivos XML abierto de Office, Office y SharePoint. Antes de unirse a Microsoft en 2005, trabajó como desarrollador durante varios años y, a continuación, inició PowerVista Software, una compañía que desarrolló y vendió un widget de cuadrícula multiplataforma. Ha escrito libros sobre control personalizado y desarrollo de GDI+. Lea su blog en blogs.msdn.com/ericwhite.