Posts Tagged ‘mapforce’
Data acquisition, часть 2
В первой части моего рассказа про data acquisition, я написал про то, какой инструментарий используется для получения HTML из интернета. В этом посте я более детально расскажу про то, как из этого HTML получать нужные данные, и как эти данные трансформировать в нужный нам формат.
Сформированность HTML
Когда вы получаете HTML из какого-то ресурса, у вас могут быть два варианта – либо идеально сформированный HTML который можно сразу конвертировать в XML (то есть брать и использовать), либо плохо сформированный HTML. Большинство HTML, к сожалению, сформировано плохо. В этой ситуации есть два варианта: либо использовать HTML Agility Pack для того чтобы вытащить все нужные данные, либо использовать эту же библиотеку для того чтобы “скорректировать” полученный HTML и сделать его более XML-образным. Вот самый минимальный пример того, как можно удалить все незакрытые элементы IMG:
var someHtml = "<p><img src='a.gif'>hello</p>";
HtmlDocument doc = new HtmlDocument();
doc.LoadHtml(someHtml);
// fix images
foreach (var node in doc.DocumentNode.SelectNodes("//img"))
if (!node.OuterHtml.EndsWith("/>"))
node.Remove();
Console.WriteLine(doc.DocumentNode.OuterHtml);
Console.ReadLine();
Кому-то может показаться, что фиксинг HTML является ненужной задачей – ведь используя тот же метод SelectNodes() можно получить любой элемент, даже если этот элемент плохо сформирован (malformed). Но существует одно приемущество, которое не следует забывать – если вы получили правильный XML, то а) вы можете сделать (или сгенерировать) XSD для этого кусочка XML; и б) получив XSD, можно генерировать мэппинги из XML-структуры на POCO, с которыми намного легче работать.
Мэппинги
Мэппинг данных обычно фигурирует в интеграционных системах вроде BizTalk. Идея в том, чтобы преобразовать набор данных во что угодно – обычно это правда просто другой набор данных. На самом деле, во многих случаях это сопоставление один-к-одному, но часто нужны разные конверсии – например, весь HTML это текст, а чтобы получить число, нужно делать конверсию (int.Parse() и т.п). Давайте посмотрим на то, как это делается.
Допустим мы получили следующую (примитивную) структуру при разборе:
<table>
<tr>
<td>Alexander</td>
<td>RD</td>
</tr>
<tr>
<td>Sergey</td>
<td>MVP, RD</td>
</tr>
<tr>
<td>Dmitri</td>
<td>MVP</td>
</tr>
</table>
А теперь представим что нам нужно замэпить эти данные на следующую структуру:
class Person
{
public string Name { get; set; }
public bool IsMVP { get; set; }
public bool IsRD { get; set; }
}
Для этого класса лучше сразу создать класс-коллекцию:
public class PersonCollection : Collection<Person> {}
Теперь мы сгенерируем XSD для исходных данных. Результат выглядит примерно вот так:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="table">
<xs:complexType>
<xs:sequence>
<xs:element name="tr" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element name="td" type="xs:string"/>
<xs:element name="td" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
Это легко – наверное слишком легко. Что сложнее так это получить схему для нашего класса коллекций. (N.b.: вместо схемы можно использовать, например, базу данных напрямую, но я пожалуй воспользуюсь XSD.) Внимание, магический трюк: компилируем сборку с типом PersonCollection а потом запускаем следующую команду:
xsd -t:PersonCollection "04 Mapping.exe"
Не поверите – эта комманда генерирует XSD на основе CLR-типа! Замечу что запускать XSD имеет смысл только в “битности” вашей системы. Не смотря на то, что у меня все компилируется для x86, чтобы заработал XSD пришлось сделать 64-битную сборку. Получился следующий XSD-файл, с помощью которого можно делать мэппинг:
<xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="ArrayOfPerson" nillable="true" type="ArrayOfPerson" />
<xs:complexType name="ArrayOfPerson">
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" name="Person" nillable="true" type="Person" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="Person">
<xs:sequence>
<xs:element minOccurs="1" maxOccurs="1" name="Name" type="xs:string" />
<xs:element minOccurs="1" maxOccurs="1" name="IsMVP" type="xs:boolean" />
<xs:element minOccurs="1" maxOccurs="1" name="IsRD" type="xs:boolean" />
</xs:sequence>
</xs:complexType>
</xs:schema>
Ну вот, у нас есть левая и правая сторона мэппинга. Сам мэппинг можно создать с помощью такого приложения как Stylus Studio или MapForce. Мэппинги создаются визуально, но процесс создания неинтуитивен, так что если вы никогда не работали с визуальными мэппингами, придется в начале немного помучаться.
Для того чтобы создать свой мэппинг, я воспользовался программой Altova MapForce. Если коротко, то эта программа может делать много разных мэппингов, в том числе и XSD-на-XSD, что нам и нужно. Мэппинги генерируются для языков XSLT1/2, XQuery, Java, C# и C++. Лично я для своих целей использую XSLT2, а для запуска трансформаций использую бесплатный движок AltovaXML, т.к. все что дает компания Microsoft в .Net для XSLT – настоящее убожество. А XQuery вообще в .Net нету. И нет, библиотека Mvp.Xml тоже не особо помогает, хотя приз за усилия разработчикам полагается.
Первое, что мы делаем – это визуально описываем мэппинг с помощью доступных нам примитивов. Выглядит результат примерно так:
Теперь мы генерируем для мэппинга XSLT. Все что осталось, так это определиться с тем как его вызывать. Если учесть что мы используем AltovaXML для трансформации, сам код выглядит вот так:
public static string XsltTransform(string xml, string xslt)
{
var app = new Application();
var x = app.XSLT2;
x.InputXMLFromText = xml;
x.XSLFromText = xslt;
return x.ExecuteAndGetResultAsString();
}
Для того, чтобы десериализовать XML в коллекцию, мы используем следующий метод:
public static T FromXml<T>(string xml) where T : class
{
var s = new XmlSerializer(typeof(T));
using (var sr = new StringReader(xml))
{
return s.Deserialize(sr) as T;
}
}
Вот собственно и все – получив наш XML, его можно смело трансформировать:
string xml = File.ReadAllText("Input.xml");
string xslt = File.ReadAllText("../../output/MappingProjectMapToPersonCollection.xslt");
string result = XsltTransform(xml, xslt);
var pc2 = FromXml<PersonCollection>(result);
Лирика о мэппингах
Кому-то может показаться что мэппинги излишни, и для простых случаев возможно это действительно так. Но хочу заметить что мэппинги, будучи дополнительным уровнем абстрации, позволяют лучше контролировать результат и адаптировать его к изменяющимся условиям – а в случае с меняющимся дизайном сайта это действительно актуально.
Мэппинги и работа с XML в целом не бесплатна – Visual Studio (даже 2010) крайне плохо с ней справляется, поэтому я воспользовался специализированной, платной программой. Хотя нет, я вру конечно, ведь мэппинги поддерживаются в BizTalk (а следовательно в VS2008). И естественно наша задача может быть “транспонирована”, в каком-то смысле, на BizTalk. А что, для личного использования можно и попробовать, если сидите на MSDN-подписке.
Вот и все на сегодня. Исходники, как всегда, тут. Comments welcome.
