Project

General

Profile

« Previous | Next » 

Revision 9956

Added by Jing Tao about 8 years ago

Add the feature to support the noNamespaceSchemalLocation and refactory the code.

View differences:

src/edu/ucsb/nceas/metacat/DocumentImpl.java
114 114
    public static final String EML200 = "eml200";
115 115
    public static final String EML210 = "eml210";
116 116
    public static final String EXTERNALSCHEMALOCATIONPROPERTY = "http://apache.org/xml/properties/schema/external-schemaLocation";
117
    public static final String EXTERNALNONAMESPACESCHEMALOCATIONPROPERTY = "http://apache.org/xml/properties/schema/external-noNamespaceSchemaLocation";
117 118
    public static final String REVISIONTABLE = "xml_revisions";
118 119
    public static final String DOCUMENTTABLE = "xml_documents";
119 120
    /*
......
2658 2659

  
2659 2660
    public static String write(DBConnection conn, String xmlString, String pub,
2660 2661
            Reader dtd, String action, String docid, String user,
2661
            String[] groups, String ruleBase, boolean needValidation, boolean writeAccessRules, byte[] xmlBytes, String formatId)
2662
            String[] groups, String ruleBase, boolean needValidation, boolean writeAccessRules, byte[] xmlBytes, String schemaLocation)
2662 2663
            throws Exception
2663 2664
    {
2664 2665
        //this method will be called in handleUpdateOrInsert method
......
2666 2667
        // get server location for this doc
2667 2668
        int serverLocation = getServerLocationNumber(docid);
2668 2669
        return write(conn, xmlString, pub, dtd, action, docid, user, groups,
2669
                serverLocation, false, ruleBase, needValidation, writeAccessRules, xmlBytes, formatId);
2670
                serverLocation, false, ruleBase, needValidation, writeAccessRules, xmlBytes, schemaLocation);
2670 2671
    }
2671 2672

  
2672 2673
    /**
......
2703 2704
    public static String write(DBConnection conn, String xmlString, String pub,
2704 2705
            Reader dtd, String action, String accnum, String user,
2705 2706
            String[] groups, int serverCode, boolean override, String ruleBase,
2706
            boolean needValidation, boolean writeAccessRules, byte[] xmlBytes, String formatId) throws Exception
2707
            boolean needValidation, boolean writeAccessRules, byte[] xmlBytes, String schemaLocation) throws Exception
2707 2708
    {
2708 2709
        // NEW - WHEN CLIENT ALWAYS PROVIDE ACCESSION NUMBER INCLUDING REV IN IT
2709 2710
    	
......
2790 2791
                    logMetacat.debug("DocumentImpl.write - initializing parser");
2791 2792
                    parser = initializeParser(conn, action, docid, xmlReader, updaterev,
2792 2793
                            user, groups, pub, serverCode, dtd, ruleBase,
2793
                            needValidation, false, null, null, encoding, writeAccessRules, guidsToSync, formatId);
2794
                            needValidation, false, null, null, encoding, writeAccessRules, guidsToSync, schemaLocation);
2794 2795
                    	// false means it is not a revision doc
2795 2796
                                   //null, null are createdate and updatedate
2796 2797
                                   //null will use current time as create date time
......
2894 2895
	        Vector<String>guidsToSync = new Vector<String>();
2895 2896

  
2896 2897
            parser = initializeParser(conn, action, docid, xmlReader, rev, user, groups,
2897
                    pub, serverCode, dtd, ruleBase, needValidation, false, null, null, encoding, writeAccessRules, guidsToSync, formatId);
2898
                    pub, serverCode, dtd, ruleBase, needValidation, false, null, null, encoding, writeAccessRules, guidsToSync, schemaLocation);
2898 2899
                    // null and null are createtime and updatetime
2899 2900
                    // null will create current time
2900 2901
                    //false means it is not a revision doc
2901 2902

  
2902 2903
            conn.setAutoCommit(false);
2903
            logMetacat.debug("DocumentImpl.write - XML to be parsed: " + xmlString);
2904
            //logMetacat.debug("DocumentImpl.write - XML to be parsed: " + xmlString);
2904 2905
            parser.parse(new InputSource(xmlReader));
2905 2906

  
2906 2907
            conn.commit();
......
3038 3039
            String pub, Reader dtd, String action, String accnum, String user,
3039 3040
            String[] groups, String homeServer, String notifyServer,
3040 3041
            String ruleBase, boolean needValidation, String tableName, 
3041
            boolean timedReplication, Date createDate, Date updateDate, String formatId) throws Exception
3042
            boolean timedReplication, Date createDate, Date updateDate, String schemaLocation) throws Exception
3042 3043
    {
3043 3044
    	// Get the xml as a string so we can write to file later
3044 3045
    	StringReader xmlReader = new StringReader(xmlString);
......
3106 3107

  
3107 3108
            parser = initializeParser(conn, action, docid, xmlReader, rev, user, groups,
3108 3109
                    pub, serverCode, dtd, ruleBase, needValidation, 
3109
                    isRevision, createDate, updateDate, encoding, writeAccessRules, guidsToSync, formatId);
3110
                    isRevision, createDate, updateDate, encoding, writeAccessRules, guidsToSync, schemaLocation);
3110 3111
         
3111 3112
            conn.setAutoCommit(false);
3112 3113
            parser.parse(new InputSource(xmlReader));
......
3725 3726
            String action, String docid, Reader xml, String rev, String user,
3726 3727
            String[] groups, String pub, int serverCode, Reader dtd,
3727 3728
            String ruleBase, boolean needValidation, boolean isRevision,
3728
            Date createDate, Date updateDate, String encoding, boolean writeAccessRules, Vector<String> guidsToSync, String formatId) throws Exception
3729
            Date createDate, Date updateDate, String encoding, boolean writeAccessRules, Vector<String> guidsToSync, String schemaLocation) throws Exception
3729 3730
    {
3730 3731
        XMLReader parser = null;
3731 3732
        try {
......
3736 3737
            // Get an instance of the parser
3737 3738
            String parserName = PropertyService.getProperty("xml.saxparser");
3738 3739
            parser = XMLReaderFactory.createXMLReader(parserName);
3739
            XMLSchemaService.getInstance().populateRegisteredSchemaList();
3740
            //XMLSchemaService.getInstance().populateRegisteredSchemaList();
3740 3741
            if (ruleBase != null && ruleBase.equals(EML200)) {
3741 3742
                logMetacat.info("DocumentImpl.initalizeParser - Using eml 2.0.0 parser");
3742 3743
                chandler = new Eml200SAXHandler(dbconn, action, docid, rev,
......
3747 3748
                parser.setErrorHandler((ErrorHandler) chandler);
3748 3749
                parser.setProperty(DECLARATIONHANDLERPROPERTY, chandler);
3749 3750
                parser.setProperty(LEXICALPROPERTY, chandler);
3750
                // turn on schema validation feature
3751
                parser.setFeature(VALIDATIONFEATURE, true);
3752 3751
                parser.setFeature(NAMESPACEFEATURE, true);
3753
                //parser.setFeature(NAMESPACEPREFIXESFEATURE, true);
3754
                parser.setFeature(SCHEMAVALIDATIONFEATURE, true);
3755
                // From DB to find the register external schema location
3756
                String externalSchemaLocation = null;
3757
//                SchemaLocationResolver resolver = new SchemaLocationResolver();
3758
                logMetacat.debug("DocumentImpl.initalizeParser - the final formatId of the object "+docid+" is "+formatId);
3759
                externalSchemaLocation = XMLSchemaService.getInstance().getNameSpaceAndLocation(formatId);
3760
                if(externalSchemaLocation == null) {
3761
                    logMetacat.info("DocumentImpl.initalizeParser - there is no register schemas for the formatid "+ formatId+". So we will use the old way."+
3762
                    " Put all registred schema/location paris for the validation.");
3763
                    externalSchemaLocation = XMLSchemaService.getInstance().getNameSpaceAndLocationStringWithoutFormatId();
3764
                    
3765
                } 
3766
                logMetacat.info("DocumentImpl.initalizeParser - 2.0.0 external schema location: " + externalSchemaLocation);
3767
                // Set external schemalocation.
3768
                if (externalSchemaLocation != null
3769
                        && !(externalSchemaLocation.trim()).equals("")) {
3770
                    parser.setProperty(EXTERNALSCHEMALOCATIONPROPERTY,
3771
                            externalSchemaLocation);
3772
                } else {
3773
                    throw new Exception ("The schema for the format id "+formatId+" can't be found in any place. So we can't validate the xml instance.");
3752
                if(needValidation) {
3753
                    logMetacat.info("DocumentImpl.initalizeParser - 2.0.0 parser sets up validation feature since the parameter of the needValidataion is "+needValidation);
3754
                    // turn on schema validation feature
3755
                    parser.setFeature(VALIDATIONFEATURE, true);
3756
                    //parser.setFeature(NAMESPACEPREFIXESFEATURE, true);
3757
                    parser.setFeature(SCHEMAVALIDATIONFEATURE, true);
3758
                    logMetacat.info("DocumentImpl.initalizeParser - 2.0.0 external schema location: " + schemaLocation);
3759
                    // Set external schemalocation.
3760
                    if (schemaLocation != null
3761
                            && !(schemaLocation.trim()).equals("")) {
3762
                        parser.setProperty(EXTERNALSCHEMALOCATIONPROPERTY,
3763
                                schemaLocation);
3764
                    } else {
3765
                        throw new Exception ("The schema for the namespace on docid "+docid+" can't be found in any place. So we can't validate the xml instance.");
3766
                    }
3774 3767
                }
3775
                logMetacat.debug("DocumentImpl.initalizeParser - 2.0.0 parser configured");
3768
                
3769
                logMetacat.info("DocumentImpl.initalizeParser - 2.0.0 parser configured");
3776 3770
            } else if (ruleBase != null && ruleBase.equals(EML210)) {
3777 3771
                logMetacat.info("DocumentImpl.initalizeParser - Using eml 2.1.0 parser");
3778 3772
                chandler = new Eml210SAXHandler(dbconn, action, docid, rev,
......
3784 3778
                parser.setProperty(DECLARATIONHANDLERPROPERTY, chandler);
3785 3779
                parser.setProperty(LEXICALPROPERTY, chandler);
3786 3780
                // turn on schema validation feature
3787
                parser.setFeature(VALIDATIONFEATURE, true);
3788 3781
                parser.setFeature(NAMESPACEFEATURE, true);
3789
                //parser.setFeature(NAMESPACEPREFIXESFEATURE, true);
3790
                parser.setFeature(SCHEMAVALIDATIONFEATURE, true);
3791
                // From DB to find the register external schema location
3792
                String externalSchemaLocation = null;
3793
                logMetacat.debug("DocumentImpl.initalizeParser - the final formatId of the object "+docid+" is "+formatId);
3794
                externalSchemaLocation = XMLSchemaService.getInstance().getNameSpaceAndLocation(formatId);
3795
                if(externalSchemaLocation == null) {
3796
                    logMetacat.info("DocumentImpl.initalizeParser - there is no register schemas for the formatid "+ formatId+". So we will use the old way."+
3797
                    " Put all registred schema/location paris for the validation.");
3798
                    externalSchemaLocation = XMLSchemaService.getInstance().getNameSpaceAndLocationStringWithoutFormatId();
3799
                    
3800
                } 
3801
                logMetacat.info("DocumentImpl.initalizeParser - 2.1.0 external schema location: " + externalSchemaLocation);
3802
                // Set external schemalocation.
3803
                if (externalSchemaLocation != null
3804
                        && !(externalSchemaLocation.trim()).equals("")) {
3805
                    parser.setProperty(EXTERNALSCHEMALOCATIONPROPERTY,
3806
                            externalSchemaLocation);
3807
                } else {
3808
                    throw new Exception ("The schema for the format id "+formatId+" can't be found in any place. So we can't validate the xml instance.");
3782
                if(needValidation) {
3783
                    logMetacat.info("DocumentImpl.initalizeParser - 2.1.0 parser sets up validation features since the parameter of the needValidataion is "+needValidation);
3784
                    parser.setFeature(VALIDATIONFEATURE, true);
3785
                    //parser.setFeature(NAMESPACEPREFIXESFEATURE, true);
3786
                    parser.setFeature(SCHEMAVALIDATIONFEATURE, true);
3787
                    logMetacat.info("DocumentImpl.initalizeParser - 2.1.0 external schema location: " + schemaLocation);
3788
                    // Set external schemalocation.
3789
                    if (schemaLocation != null
3790
                            && !(schemaLocation.trim()).equals("")) {
3791
                        parser.setProperty(EXTERNALSCHEMALOCATIONPROPERTY,
3792
                                schemaLocation);
3793
                    } else {
3794
                        throw new Exception ("The schema for the docid "+docid+" can't be found in any place. So we can't validate the xml instance.");
3795
                    }
3809 3796
                }
3810 3797
                logMetacat.debug("DocumentImpl.initalizeParser - Using eml 2.1.0 parser configured");
3811 3798
            } else {
......
3824 3811
                        && needValidation) {
3825 3812
                
3826 3813
                    XMLSchemaService xmlss = XMLSchemaService.getInstance();
3827
                    xmlss.doRefresh();
3814
                    //xmlss.doRefresh();
3828 3815
                    logMetacat.info("DocumentImpl.initalizeParser - Using General schema parser");
3829 3816
                    // turn on schema validation feature
3830 3817
                    parser.setFeature(VALIDATIONFEATURE, true);
......
3838 3825
                    if (xmlss.useFullSchemaValidation() && !allSchemasRegistered) {
3839 3826
                    	parser.setFeature(FULLSCHEMAVALIDATIONFEATURE, true);
3840 3827
                    }
3841
                    // From DB to find the register external schema location
3842
                    String externalSchemaLocation = null;
3843
                    logMetacat.debug("DocumentImpl.initalizeParser - the final formatId of the object "+docid+" is "+formatId);
3844
                    externalSchemaLocation = XMLSchemaService.getInstance().getNameSpaceAndLocation(formatId);
3845
                    if(externalSchemaLocation == null) {
3846
                        logMetacat.info("DocumentImpl.initalizeParser - there is no register schemas for the formatid "+ formatId+". So we will use the old way."+
3847
                        " Put all registred schema/location paris for the validation.");
3848
                        externalSchemaLocation = XMLSchemaService.getInstance().getNameSpaceAndLocationStringWithoutFormatId();
3849
                        
3850
                    } 
3851
                    logMetacat.info("DocumentImpl.initalizeParser - Generic external schema location: " + externalSchemaLocation);              
3828
                    logMetacat.info("DocumentImpl.initalizeParser - Generic external schema location: " + schemaLocation);              
3852 3829
                    // Set external schemalocation.
3853
                    if (externalSchemaLocation != null
3854
                            && !(externalSchemaLocation.trim()).equals("")) {
3830
                    if (schemaLocation != null
3831
                            && !(schemaLocation.trim()).equals("")) {
3855 3832
                        parser.setProperty(EXTERNALSCHEMALOCATIONPROPERTY,
3856
                                externalSchemaLocation);
3833
                                schemaLocation);
3857 3834
                    } else {
3858
                        throw new Exception ("The schema for the format id "+formatId+" can't be found in any place. So we can't validate the xml instance.");
3835
                        throw new Exception ("The schema for the document "+docid+" can't be found in any place. So we can't validate the xml instance.");
3859 3836
                    }
3860

  
3837
                } else if (ruleBase != null && ruleBase.equals(NONAMESPACESCHEMA)
3838
                        && needValidation) {
3839
                    //xmlss.doRefresh();
3840
                    logMetacat.info("DocumentImpl.initalizeParser - Using General schema parser");
3841
                    // turn on schema validation feature
3842
                    parser.setFeature(VALIDATIONFEATURE, true);
3843
                    parser.setFeature(NAMESPACEFEATURE, true);
3844
                    //parser.setFeature(NAMESPACEPREFIXESFEATURE, true);
3845
                    parser.setFeature(SCHEMAVALIDATIONFEATURE, true);
3846
                    logMetacat.info("DocumentImpl.initalizeParser - Generic external no-namespace schema location: " + schemaLocation);              
3847
                    // Set external schemalocation.
3848
                    if (schemaLocation != null
3849
                            && !(schemaLocation.trim()).equals("")) {
3850
                        parser.setProperty(EXTERNALNONAMESPACESCHEMALOCATIONPROPERTY,
3851
                                schemaLocation);
3852
                    } else {
3853
                        throw new Exception ("The schema for the document "+docid+" can't be found in any place. So we can't validate the xml instance.");
3854
                    }
3861 3855
                } else if (ruleBase != null && ruleBase.equals(DTD)
3862 3856
                        && needValidation) {
3863 3857
                    logMetacat.info("DocumentImpl.initalizeParser - Using dtd parser");
src/edu/ucsb/nceas/metacat/DocumentImplWrapper.java
59 59
	}//Constructor
60 60

  
61 61
	public String write(DBConnection conn, String xml, String pub, Reader dtd,
62
			String action, String docid, String user, String[] groups, byte[]xmlBytes, String formatId) throws Exception {
62
			String action, String docid, String user, String[] groups, byte[]xmlBytes, String schemaLocalLocation) throws Exception {
63 63
		return DocumentImpl.write(conn, xml, pub, dtd, action, docid, user, groups,
64
				ruleBase, needValidation, writeAccessRules, xmlBytes, formatId);
64
				ruleBase, needValidation, writeAccessRules, xmlBytes, schemaLocalLocation);
65 65
	}
66 66

  
67 67
	public String writeReplication(DBConnection conn, String xml, byte[]xmlBytes, String pub, Reader dtd,
......
72 72
		// so rule base is null and need validation is false (first false)
73 73
		// this method is for force replication. so the table name is xml_documents
74 74
		// and timed replication is false (last false)
75
	    String schemaLocation = null;
76
	    boolean needValidate = false;
75 77
		return DocumentImpl.writeReplication(conn, xml, xmlBytes, pub, dtd, action, accnum, user,
76
				groups, homeServer, notifyServer, ruleBase, false,
77
				DocumentImpl.DOCUMENTTABLE, false, createDate, updateDate, formatId);
78
				groups, homeServer, notifyServer, ruleBase, needValidate,
79
				DocumentImpl.DOCUMENTTABLE, false, createDate, updateDate, schemaLocation);
78 80
		// last false means is not timed replication
79 81

  
80 82
	}
......
103 105
			throws Exception {
104 106
		//we don't need to check validation in replication
105 107
		// so rule base is null and need validation is false
108
	    String schemaLocation = null;
109
	    boolean needValidate = false;
106 110
		return DocumentImpl.writeReplication(conn, xml, xmlBytes, pub, dtd, action, accnum, user,
107
				groups, homeServer, notifyServer, ruleBase, false, tableName,
108
				timedReplication, createDate, updateDate, formatId);
111
				groups, homeServer, notifyServer, ruleBase, needValidate, tableName,
112
				timedReplication, createDate, updateDate, schemaLocation);
109 113
	}
110 114

  
111 115
}
src/edu/ucsb/nceas/metacat/MetacatHandler.java
1722 1722
          boolean validate = false;
1723 1723
          DocumentImplWrapper documentWrapper = null;
1724 1724
          String namespace = null;
1725

  
1725
          String schemaLocation = null;
1726 1726
          try {
1727 1727
            // look inside XML Document for <!DOCTYPE ... PUBLIC/SYSTEM ...
1728 1728
            // >
......
1730 1730
            validate = needDTDValidation(xmlReader);
1731 1731
            if (validate) {
1732 1732
                // set a dtd base validation parser
1733
                logMetacat.debug("MetacatHandler.handleInsertOrUpdateAction - the xml object will be validate by a dtd");
1733 1734
                String rule = DocumentImpl.DTD;
1734 1735
                documentWrapper = new DocumentImplWrapper(rule, validate, writeAccessRules);
1735 1736
            } else {
1736
                
1737
                XMLSchemaService.getInstance().doRefresh();
1737 1738
                namespace = XMLSchemaService.findDocumentNamespace(xmlReader);
1738
                
1739 1739
                if (namespace != null) {
1740
                    logMetacat.debug("MetacatHandler.handleInsertOrUpdateAction - the xml object will be validated by a schema which has a target namespace: "+namespace);
1741
                    schemaLocation = XMLSchemaService.getInstance().findNamespaceAndSchemaLocalLocation(formatId, namespace);
1740 1742
                    if (namespace.compareTo(DocumentImpl.EML2_0_0NAMESPACE) == 0
1741 1743
                            || namespace.compareTo(
1742 1744
                            DocumentImpl.EML2_0_1NAMESPACE) == 0) {
......
1756 1758
                        EMLParser parser = new EMLParser(doctext[0]);
1757 1759
                        documentWrapper = new DocumentImplWrapper(rule, true, writeAccessRules);
1758 1760
                    } else {
1761
                        if(!XMLSchemaService.isNamespaceRegistered(namespace)) {
1762
                            throw new Exception("The namespace "+namespace+" used in the xml object hasn't been registered in the Metacat. Metacat can't validate the object and rejected it. Please contact the operator of the Metacat for regsitering the namespace.");
1763
                        }
1759 1764
                        // set schema base validation parser
1760 1765
                        String rule = DocumentImpl.SCHEMA;
1761 1766
                        documentWrapper = new DocumentImplWrapper(rule, true, writeAccessRules);
1762 1767
                    }
1763 1768
                } else {
1764
                    documentWrapper = new DocumentImplWrapper("", false, writeAccessRules);
1769
                    xmlReader = new StringReader(doctext[0]);
1770
                    String noNamespaceSchemaLocationAttr = XMLSchemaService.findNoNamespaceSchemaLocationAttr(xmlReader);
1771
                    if(noNamespaceSchemaLocationAttr != null) {
1772
                        logMetacat.debug("MetacatHandler.handleInsertOrUpdateAction - the xml object will be validated by a schema which deoe NOT have a target namespace.");
1773
                        schemaLocation = XMLSchemaService.getInstance().findNoNamespaceSchemaLocalLocation(formatId, noNamespaceSchemaLocationAttr);
1774
                        String rule = DocumentImpl.NONAMESPACESCHEMA;
1775
                        documentWrapper = new DocumentImplWrapper(rule, true, writeAccessRules);
1776
                    } else {
1777
                        logMetacat.debug("MetacatHandler.handleInsertOrUpdateAction - the xml object will NOT be validated.");
1778
                        documentWrapper = new DocumentImplWrapper("", false, writeAccessRules);
1779
                    }
1780
                    
1765 1781
                }
1766 1782
            }
1767 1783
            
......
1805 1821
              
1806 1822
              } else {*/
1807 1823
              newdocid = documentWrapper.write(dbConn, doctext[0], pub, dtd,
1808
                          doAction, accNumber, user, groups, xmlBytes, formatId);
1824
                          doAction, accNumber, user, groups, xmlBytes, schemaLocation);
1809 1825
            
1810 1826
              EventLog.getInstance().log(ipAddress, userAgent, user, accNumber, action[0]);
1811 1827
              
......
1909 1925
            output += e.getMessage();
1910 1926
            output += this.ERRORCLOSE;
1911 1927
            logMetacat.warn("MetacatHandler.handleInsertOrUpdateAction - " +
1912
            		        "General error when writing eml " +
1928
            		        "General error when writing the xml object " +
1913 1929
            		        "document to the database: " + 
1914 1930
            		        e.getMessage());
1915 1931
            e.printStackTrace();
src/edu/ucsb/nceas/metacat/util/EMLVersionsTransformer.java
41 41
import edu.ucsb.nceas.metacat.database.DBConnection;
42 42
import edu.ucsb.nceas.metacat.database.DBConnectionPool;
43 43
import edu.ucsb.nceas.metacat.properties.PropertyService;
44
import edu.ucsb.nceas.metacat.service.XMLSchemaService;
44 45

  
45 46
import javax.xml.transform.TransformerFactory;
46 47
import javax.xml.transform.Transformer;
......
49 50
import javax.xml.transform.TransformerException;
50 51
import javax.xml.transform.TransformerConfigurationException;
51 52
import javax.xml.transform.URIResolver;
53

  
52 54
import org.ecoinformatics.eml.EMLParser;
53 55

  
54 56

  
......
165 167
            	 dbconn = DBConnectionPool
166 168
                 .getDBConnection("EMLVersionsTransformer.handleSingleEML200Document");
167 169
                  serialNumber = dbconn.getCheckOutSerialNumber();
170
                  String schemaLocation = XMLSchemaService.getInstance().getNameSpaceAndLocationStringWithoutFormatId();
168 171
                  documentWrapper.write(dbconn, eml210Content, pub, dtd,
169
                          doAction, newId, owner, groups, null, formatId);
172
                          doAction, newId, owner, groups, null, schemaLocation);
170 173
                  logMetacat.warn("Doc "+docidWithRev+" was transformed to eml210 with new id "+newId);
171 174
                  transformLog("Doc "+docidWithRev+" was transformed to eml210 with new id "+newId);
172 175
             }

Also available in: Unified diff