代码之家  ›  专栏  ›  技术社区  ›  glebm

用于转换结构的XSLT+用于转换值的Ruby?

  •  3
  • glebm  · 技术社区  · 15 年前

    我们有相当大的(~200MB)XML文件,它们来自不同的源,我们想要转换成一种通用格式。

    对于结构转换(元素名、嵌套等),我们决定使用XSLT(1.0)。因为它必须很快(我们收到了很多这样的文件),所以我们选择了ApacheXalan作为引擎。结构转换可能非常复杂(不仅仅是 <tag a> -> <tag b> )和对于来自不同源的XML文件是不同的。

    但是,我们还需要转换元素的值。转换可能相当复杂(例如,有些需要访问Google Maps API,有些需要访问我们的数据库等等),因此我们决定使用一个简单的基于Ruby的DSL,它是“xpath selector”=>转换器实体的列表,即:

    {"rss/channel/item" => {:class => 'ItemMutators', :method => :guess_location}

    然而,将元素转换与价值转换分开似乎更像是一种黑客行为。有更好的解决方案吗?


    例如,使用Java可以为XLAN编写扩展,并且可以使用它们来对值进行转换。 除了红宝石还有什么相似的吗?


    谢谢你们,伙计们! 所有的回答都很有价值。我现在在想:)

    3 回复  |  直到 15 年前
        1
  •  2
  •   Aryabhatta    15 年前

    您应该能够使用XSLT扩展。Web搜索显示XLAN支持Java进行扩展: http://xml.apache.org/xalan-j/extensions.html

    从链接页引用:

    在那种情况下 喜欢增强 带过程调用的XSLT 语言,XalaJava支持 扩展元素的创建和使用 以及扩展函数。撒兰爪哇 还提供了不断增长的扩展 图书馆供您使用。

    另外,显然有人用Ruby编写了一个包,可以提供XSLT扩展: http://greg.rubyfr.net/pub/packages/ruby-xslt/classes/XML/XSLT.html

        2
  •  2
  •   martinr    15 年前

    我将用Ruby来完成这一切,编写一个可以完成两个任务的模块:

    1)在Ruby中执行各种XML输入格式的SAX解析,输出中间格式的XML文档和验证错误/密钥冲突错误列表。

    2)从中间格式xml文件创建一个dom树,在适当的位置进行修改,使用导入的数据进行增强,并将修改后的dom树输出为标准格式。

    使用SAX的第一步允许从文件中剥离冗余数据(而不是加载到DOM模型中!)而非冗余的数据组则需要快速地放置在统一命名的标签中。为了达到最大速度,在输出到中间格式XML之前,不应以任何方式对数据组进行排序,并且中间格式XML应使用短标记名。

    使用DOM的第二步允许对中间格式XML的标记进行快速排序和处理,其中没有发现任何验证错误。

    这里的validation error指的是一系列的东西,例如丢失的字段或无效的键格式、数字范围等。它还可以检测文件中丢失的键所引用的对象;为此,它建立了两个哈希,一个是被引用的键,另一个是当前的键,并根据当前的键检查被引用的键作为完成前的AST步骤。虽然您可以使用XSD或DTD进行一些检查,但Ruby允许您进行更多的灵活性,并且实践中的许多验证问题都是“较软”的错误,对于这些错误可以进行一些有限的更正。

    模块应该限制并行完成每个任务的数量,以避免系统耗尽CPU或RAM。

    我的建议的本质是用Ruby来完成这一切,但是要将工作分为两个阶段——第一阶段,那些可以用SAX快速完成的任务和第二阶段,那些可以用DOM快速完成的任务。

    编辑

    >我们如何使用SAX进行结构转换?

    嗯,你不能做任何类型的重新排序方便或Eeles,你已经不再真正使用内存串行解析语法的好处,但这是一个例子的方式,我的意思是第一阶段,使用Java(对不起不是Ruby,但应该相当容易翻译-认为这是伪代码!)以下内容:

    class MySAXHandler implements org.xml.sax.ContentHandler extends Object {
      final static int MAX_DEPTH=512;
      final static int FILETYPE_A=1;
      final static int FILETYPE_B=2;
      String[] qualifiedNames = new String[MAX_DEPTH];
      String[] localNames = new String[MAX_DEPTH];
      String[] namespaceURIs = new String[MAX_DEPTH];
      int[] meaning = new int[MAX_DEPTH];
      int pathPos=0;
      public java.io.Writer destination;
      ArrayList errorList=new ArrayList();
      org.xml.sax.Locator locator;
      public int inputFileSchemaType;
    
      String currentFirstName=null;
      String currentLastName=null;
    
      puiblic void setDocumentLocator(org.xml.sax.Locator l) { this.locator=l; }
    
      public void startElement(String uri, String localName, String qName,
        org.xml.sax.Attributes atts) throws SAXException { 
    
        // record current tag in stack
        qualifiedNames[pathPos] = qName;
        localNames[pathPos] = localName;
        namespaceURIs[pathPos] = uri;
        int meaning;
    
        // what is the meaning of the current tag?
        meaning=0; pm=pathPos==0?0:meanings[pathPos-1];
        switch (inputFileSchemaType) {
               case FILETYPE_A:
          switch(pathPos) {
            // this checking can be as strict or as lenient as you like on case,
            // namespace URIs and tag prefixes
                 case 0:
            if(localName.equals("document")&&uri.equals("http://xyz")) meaning=1;
          break; case 1: if (pm==1&&localName.equals("clients")) meaning=2;
          break; case 2: if (pm==2&&localName.equals("firstName")) meaning=3;
            else if (pm==2&&localName.equals("lastName")) meaning=4;
            else if (pm==2) meaning=5;
          }
          break; case FILETYPE_B:
          switch(pathPos) {
            // this checking can be as strict or as lenient as you like on case,
            // namespace URIs and tag prefixes
                 case 0:
            if(localName.equals("DOC")&&uri.equals("http://abc")) meaning=1;
          break; case 1: if (pm==1&&localName.equals("CLS")) meaning=2;
          break; case 2: if (pm==2&&localName.equals("FN1")) meaning=3;
            else if (pm==2&&localName.equals("LN1")) meaning=4;
            else if (pm==2) meaning=5;
          }
        }
    
        meanings[pathPos]=meaning;
    
        // does the tag have unrecognised attributes?
        // does the tag have all required attributes?
        // record any keys in hashtables...
        // (TO BE DONE)
    
        // generate output
        switch (meaning) {
          case 0:errorList.add(new Object[]{locator.getPublicId(),
            locator.getSystemId(),
            locator.getLineNumber(),locator.getColumnNumber(),
            "Meaningless tag found: "+localName+" ("+qName+
            "; namespace: \""+uri+"\")});
          break;case 1:
          destination.write("<?xml version=\"1.0\" ?>\n");
          destination.write("<imdoc xmlns=\"http://someurl\" lang=\"xyz\">\n");
          destination.write("<!-- Copyright notice -->\n");
          destination.write("<!-- Generated by xyz -->\n");
          break;case 2: destination.write(" <cl>\n");
            currentFirstName="";currentLastName="";
        }
        pathPos++;
      }
      public void characters(char[] ch, int start, int length)
                throws SAXException {
        int meaning=meanings[pathPos-1]; switch (meaning) {
        case 1: case 2:
                  errorList.add(new Object[]{locator.getPublicId(),
            locator.getSystemId(),
            locator.getLineNumber(),locator.getColumnNumber(),
            "Unexpected extra characters found"});
        break; case 3:
          // APPEND to currentFirstName IF WITHIN SIZE LIMITS
        break; case 4:
          // APPEND to currentLastName IF WITHIN SIZE LIMITS
        break; default: // ignore other characters
        }
      }
      public void endElement(String uri, String localName, String qName)
        throws SAXException {
        pathPos--;
        int meaning=meanings[pathPos]; switch (meaning) { case 1:
          destination.write("</imdoc>");
        break; case 2:
          destination.write("  <ln>"+currentLastName.trim()+"</ln>\n");
          destination.write("  <fn>"+currentFirstName.trim()+"</fn>\n");
          destination.write(" </cl>\n");
        break; case 3:
          if (currentFirstName==null||currentFirstName.equals(""))
                  errorList.add(new Object[]{locator.getPublicId(),
            locator.getSystemId(),
            locator.getLineNumber(),locator.getColumnNumber(),
            "Invalid first name length"});
          // ADD FIELD FORMAT VALIDATION USING REGEXES / RANGE CHECKING
        break; case 4:
          if (currentLastName==null||currentLastName.equals(""))
                  errorList.add(new Object[]{locator.getPublicId(),
            locator.getSystemId(),
            locator.getLineNumber(),locator.getColumnNumber(),
            "Invalid last name length"});
          // ADD FIELD FORMAT VALIDATION USING REGEXES / RANGE CHECKING
        }
      }
      public void endDocument() {
        // check for key violations
      }
    }
    

    第一阶段的代码不用于重新排序数据,只是标准化为单个中间格式(可以承认,数据组的顺序可能会因源文件类型而异,因为数据组的顺序将与源文件的顺序相同),并对其进行验证。

    但是,只有当您对您的XSLT不满意时,编写SAX处理程序才是值得的。如果你在写这个问题的话,你大概不是…?

    如果您喜欢您的XSLT,并且它运行得足够快,我会说为什么要更改架构。在这种情况下,你可能会发现{ this }如果您还没有在Ruby模块中包装相关的xalan调用,那么本文将非常有用。您可能希望尝试将其作为用户的一步过程(对于没有发现数据错误的情况!).

    编辑

    使用这种方法,您必须在输出时手动转义XML,以便:

    &变成&amp;

    >变成>

    <变成<

    如果需要,非ASCII将成为字符实体,否则将成为UTF-8序列

    还值得编写一个函数,该函数可以将SAX属性对象和与输入标记的含义和文件格式相关的灵活验证规范作为对象数组或类似对象,并且可以严格或宽松地根据需要匹配和返回值,并标记错误。

    最后,您应该有一个可配置的最大错误概念,默认值为1000,在此限制下记录“太多错误”错误,并在达到限制后停止记录错误。

    如果您需要增加可以并行执行的XML的数量,并且仍在努力提高容量/性能,我建议DOM步骤只加载、重新排序和保存,因此一次可以执行一个或两个文档,但相对较快地成批执行,然后再执行第二个SAX处理器,然后Google调用并为n连续处理XML。并行文档。

    高温高压

    编辑

    >我们有大约50种不同的输入格式,所以

    >switch/case格式不好。

    当然,这是传统的智慧,但以下几点呢?

    // set meaning and attributesValidationRule (avr)
    if (fileFormat>=GROUP10) switch (fileFormat) {
      case GROUP10_FORMAT1: 
    
        switch(pathPos) {
        case 0: if (...) { meaning=GROUP10_CUSTOMER; avr=AVR6_A; }
        break; case 1: if (...) { meaning=...; avr=...; }
        ...
        }
    
      break; case GROUP10_FORMAT2: ...
    
      break; case GROUP10_FORMAT3: ...
    }
    else if (fileFormat>=GROUP9) switch (fileFormat) {
      case GROUP9_FORMAT1: ... 
      break; case GROUP9_FORMAT2: ...
    }
    ...
    else if (fileFormat>=GROUP1) switch (fileFormat) {
      case GROUP1_FORMAT1: ... 
      break; case GROUP1_FORMAT2: ...
    }
    
    ...
    
    result = validateAttribute(atts,avr);
    
    if (meaning >= MEANING_SET10) switch (meaning) {
    case ...:  ...
    break; case ...:  ...
    }
    else if (meaning >= MEANING_SET9) switch (meaning) {
    }
    etc
    

    很可能比很多函数或类都快,也更容易阅读。

    >我不高兴的是我不能做结构

    >以及使用某种同构过程进行的价值转换

    >(与Java一样,我可以为XLAN编写扩展)。

    听起来您已经达到了XSLT的极限,或者您只是在谈论从源文档以外的源引入数据这一明显的极限是一种痛苦吗?

    另一个想法是创建一个验证样式表,一个输出用于尝试Google地图的键列表的样式表,一个输出用于尝试数据库的键列表的样式表,处理实际执行Google/DB调用并输出更多XML的过程,一个“XML连接”函数,以及一个组合数据的样式表,采用如下输入:

    <?xml version="1.0" ?>
    <myConsolidatedInputXmlDoc>
      <myOriginalOrIntermediateFormatDoc>
        ...
      </myOriginalOrIntermediateFormatDoc>
      <myFetchedRelatedDataFromGoogleMaps>
        ...
      </myFetchedRelatedDataFromGoogleMaps>
      <myFetchedDataFromSQL>
        ...
      </myFetchedDataFromSQL>
    </myConsolidatedInputXmlDoc>
    

    这样,您就可以在“多通道”场景中使用XSLT,而无需调用Xalan扩展。

        3
  •  1
  •   Lachlan Roche    15 年前

    一种方法是将xalan-j与一些扩展一起使用,这些扩展可以使rpc回调Ruby进程。返回的数据可以由XSLT进一步处理。

    为了实现更紧密的集成,可以将xalan-c++绑定为Ruby 图书馆。您可能只需要xalan api的一小部分,类似于命令行驱动程序xalanexe中使用的部分。随着xalan在进程中运行,您的扩展可以 直接访问您的Ruby模型。

    链接:

    推荐文章